转自:点击打开链接
TCP程序
TCPServer
- // TCPServer.cpp : 定义控制台应用程序的入口点。
- //接收客户的发来的"MyTCP"
- #include <stdio.h>
- #include <WinSock2.h>
- #pragma comment(lib,"ws2_32.lib")
- #define BUF_SIZE 64
- void main()
- {
- WSADATA wsd;
- if (WSAStartup(MAKEWORD(2,2),&wsd) != 0)//初始化套接字动态库
- {
- printf("WSAStartup() failed! erron=%d\n",GetLastError());
- return;
- }
- SOCKET sServ;
- if ((sServ=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP)) == INVALID_SOCKET)//创建套接字
- {
- printf("socket() failed! errno=%d\n",WSAGetLastError());
- WSACleanup();
- return;
- }
- //绑定服务器地址
- SOCKADDR_IN addrServ;
- addrServ.sin_family=AF_INET;
- addrServ.sin_addr.S_un.S_addr=INADDR_ANY;
- addrServ.sin_port=htons(5000);
- if (bind(sServ,(SOCKADDR*)&addrServ,sizeof(addrServ)) == SOCKET_ERROR)
- {
- printf("bind() failed!errno=%d\n",WSAGetLastError());
- closesocket(sServ);//关闭套接字
- WSACleanup();//释放套接字资源
- return;
- }
- if (listen(sServ,2) == SOCKET_ERROR)//开始监听
- {
- printf("listen() failed! errno=%\n",WSAGetLastError());
- closesocket(sServ);
- WSACleanup();
- return;
- }
- SOCKADDR_IN addrClient;
- SOCKET sClient;
- int len=sizeof(addrClient);
- if ((sClient=accept(sServ,(SOCKADDR*)&addrClient,&len)) == INVALID_SOCKET)//接收客户端连接
- {
- printf("accept() failed! error=%d\n",WSAGetLastError());
- closesocket(sServ);
- WSACleanup();
- return;
- }
- //接收客户端数据
- char buf[BUF_SIZE];
- ZeroMemory(buf,BUF_SIZE);
- if (recv(sClient,buf,BUF_SIZE,0) == SOCKET_ERROR)
- {
- printf("recv() failed! error=%d\n",WSAGetLastError());
- closesocket(sClient);
- closesocket(sServ);
- WSACleanup();
- return;
- }
- printf("%s\n",buf);//输出“MyTCP”
- closesocket(sClient);
- closesocket(sServ);
- WSACleanup();
- system("pause");
- return;
- }
- //相对完整的http://blog.youkuaiyun.com/ouyangshima/article/details/8932334
- /*
- TCP编程的服务器端一般步骤是
- 1.创建一个socket,用函数socket();
- 2.设置socket属性,用函数setsockopt(); //可选
- 3.绑定IP地址,端口等信息到socket上,用函数bind();
- 4.开启监听,用函数listen();
- 5.接收客户端上的来的连接,用函数accept();
- 6.收到数据用send()和recv(),或者read()和write();
- 7.关闭网络连接
- 8.关闭监听
- 服务器端 socket-->bind-->listen-->accept
- */
TCPClient
- // TCPClient.cpp : 定义控制台应用程序的入口点。
- //功能:向服务器端发送“MyTCP”
- #include <stdio.h>
- #include <WinSock2.h>
- #pragma comment(lib,"ws2_32.lib")
- #define BUF_SIZE 64
- void main()
- {
- WSADATA wsd;
- if (WSAStartup(MAKEWORD(2,2),&wsd) != 0)//初始化套接字动态库
- {
- printf("WSAStartup() failed! erron=%d\n",GetLastError());
- return;
- }
- SOCKET sHost;//服务器套接字
- if ((sHost=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP)) == INVALID_SOCKET)//创建套接字
- {
- printf("socket() failed! errno=%d\n",WSAGetLastError());
- WSACleanup();//释放套接字资源
- return;
- }
- //设置服务器地址
- SOCKADDR_IN servAddr;
- servAddr.sin_family=AF_INET;
- servAddr.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
- servAddr.sin_port=htons(5000);
- if (connect(sHost,(SOCKADDR*)&servAddr,sizeof(servAddr)) == SOCKET_ERROR)//连接套接字
- {
- printf("connect() failed! errno=%d\n",WSAGetLastError());
- closesocket(sHost);//关闭套接字
- WSACleanup();
- return;
- }
- //向服务器发送数据
- char buf[BUF_SIZE];
- ZeroMemory(buf,BUF_SIZE);
- strcpy(buf,"MyTCP");
- if (send(sHost,buf,strlen(buf),0) == SOCKET_ERROR)
- {
- printf("send() failed! erron=%d\n",WSAGetLastError());
- closesocket(sHost);
- WSACleanup();
- return;
- }
- //退出
- closesocket(sHost);
- WSACleanup();
- system("pause");
- return;
- }
- //相对完整的http://blog.youkuaiyun.com/ouyangshima/article/details/8922575
- /*
- TCP编程的客户端一般步骤是
- 1.创建一个socket,用函数sockect();
- 2.设置socket属性,用函数setsockopt(); //可选
- 3.绑定IP地址,端口等信息到socket上,用函数bind(); //可选
- 4.设置要连接的对方的IP地址和端口等属性;
- 5.连接服务器,用函数connect();
- 6.收到数据,用函数send()和recv()或者read()和write();
- 7.关闭网络连接
- 客户端 socket-->connect
- */
TCP API
- /*
- 1.WSAStartup()函数
- WSAStartup():功能是加载ws2_32.dll等socket程序运行环境的动态库(DLL)。在程序初始化后,socket程序运行所依赖的动态链接库不一定已经加载,WSAStartup保证了Socket动态链接库的加载。
- int WSAStartup(_in Word wVersionRequested,_out LPWSDATA lpWSAData);
- wVersionRequested:是Socket程序库的版本。高字节指定所需要库文件的副版本,低字节指定主版本。在程序中可以使用MAKEWORD(X,Y)方便指定参数,其中X是指高位字节,Y是指低位字节。一般使用MAKEWORD(2,2)宏
- lpWSAData:输出参数,指向WSADATA结构的指针,用于返回scoket库初始化的信息
- 返回值:0表示成功,
- WSACleanup():与WSAStartup()的功能相反,WSACleanup释放ws2_32.dll库。
- WSADATA wsaData;
- LPVOID recvbuf;
- if (WSAStartup(MAKEWORD(2,2),&wsaData) != NO_ERROR)
- {
- printf("Error at WSAStartup()\n");
- }
- if(LOBYTE(wsaData.wVersion)!=2 || HIBYTE(wsaData.wVersion) != 2)//检查返回的DLL是否为2.2
- {
- //没有找到可用的DLL
- WSACleanup();
- return;
- }
- 2.socket()函数
- SOCKET socket( int af, int type, int protocol ); 等价于WSASocket()
- 参数:int af:协议的地址家族(AF_UNIX,AF_INET等),AF_UNIX只能够用于单一的Unix系统进程间的通信,而AF_INET是针对Internet的,因此可以允许远程——本机之间的通信。AF:Address families(地址协议族)。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。创建TCP或者UDP套接字时,该参数为AF_INET
- int type:协议的套接字类型:有SOCK_STREAM(TCP),SOCK_DGRAM(UDP),SOCK_RAM 3种
- int protocol:指定协议(有IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议),由于我们指定了type,所以这个地方我们一般只要用0来代替就可以了
- 返回值:若成功返回SOCKET对象标识,SOCKET就是unsigned int;如失败,则返回INVALID_SOCKET,调用WSAGetLastError()可得知原因,所有WinSocket的函数都可以使用这个函数来获取失败的原因。
- 注意:并不是上面的type和protocol可以随意组合的,如SOCK_STREAM不可以跟IPPROTO_UDP组合。当protocol为0时,会自动选择type类型对应的默认协议。
- 当我们调用socket创建一个socket时,返回的socket描述字它存在于协议族(address family,AF_XXX)空间中,但没有一个具体的地址。如果想要给它赋值一个地址,就必须调用bind()函数,否则就当调用connect()、listen()时系统会自动随机分配一个端口。
- 3.bind()函数
- 当我们调用socket创建一个socket时,返回的socket描述字它存在于协议族(address family,AF_XXX)空间中,但没有一个具体的地址。如果想要给它赋值一个地址,就必须调用bind()函数,否则就当调用connect()、listen()时系统会自动随机分配一个端口。
- bind()函数把一个地址族中的特定地址赋给socket。例如,对应AF_INET、AF_INET6就是把一个ipv4或ipv6地址和端口号组合赋给socket。
- int bind(SOCKET s,sockaddr * name,int namelen);
- 参 数: s:Socket对象名,它通过socket()创建了,唯一标识一个socket
- name:地址,服务器地址信息名称(包含信息有:地址协议族,服务器本机的IP,要监听的端口)
- namelen:sockaddr的结构长度
- 返回值:成功返回0,否则返回SOCKET_ERROR
- 通常服务器在启动的时候会绑定一个众说周知的地址(如ip地址+端口号),用于提供服务,客户就可以通过它(ip+port)来连接服务器;而客户端就不用指定,有系统自动分配一个端口号和自身的ip地址组合。这就是为什么通常服务器端在listen之前会调用bind(),而客户端就不用调用,而是在connect()时由系统随机生成一个。
- SOCKET s;
- SOCKADDR_IN servAddr;
- //定义服务器地址
- servAddr.sin_family=AF_INET;
- servAddr.sin_addr.S_un.S_addr=htonl(INADDR_ANY);//如果程序不关心分配给它的地址,则可将地址设置为INADDR_ANY,可以使用任意网络接口。
- servAddr.sin_port=htons(2222);
- if (bind(s,(SOCKADDR*)&servAddr,sizeof(servAddr)) == SOCKET_ERROR)//绑定服务器地址
- {
- //绑定套接字失败
- }
- 4.listen()函数
- int listen(SOCKET s,int backlog);将套接字设置为监听模式
- 参 数: s:一个已绑定的套接字
- backlog:指定等待连接的最大队列长度(一般2~4,用SOMAXCONN则有系统确定)。socket可以排队的最大连接个数,不是最多可以连接几个客户端。更具体些:TCP模块允许的已完成三次握手过程(TCP模块完成)但还没来得及被应用程序accep()的最大连接数。eg:listen(socketID,3);如果有4个客户端同时向服务器发出请求,那么前3个连接会被放到等待处理的队列中,以便服务器依次为他们服务,而第4个连接将会造成WSAECONNREFUSED错误。当服务器接受了一个连接请求时,这个请求就从队列中删除。
- 返回值:成功返回0,否则返回SOCKET_ERROR
- socket()函数创建的socket默认是一个主动类型的,listen()函数则将主动连接套接口socket变为被动连接套接口,使得这个进程可以接受其他进程的请求(客户的连接请求),从而成为一个服务器进程。
- 5.accept()函数
- SOCKET accept(SOCKET s,_output struct sockaddr * addr,_output int * addrlen);
- 参数:s:监听套接字,
- addr:存放来连接的客户端的地址和端口,(若客户端使用了bind()来绑定客户端本地的IP和Port,则服务器端会得到客户端bind的端口,而不是服务器端自动分配的端口)。当我们调用socket()创建一个socket时,返回的socket描述字它存在于协议族(address family,AF_XXX)空间中,但没有一个具体的地址。如果想要给它赋值一个地址,就必须调用bind()函数,否则就当调用connect()、listen()时系统会自动随机分配一个端口。若对客户端的IP地址不感兴趣,则可以设为NULL
- addrlen:返回addr结构的长度,sizeof(SOCKADDR_IN),当addr为NULL时,addrlen则可以为NULL
- 返回值:功返回一个新产生的Socket对象,否则返回INVALID_SOCKET
- accept()默认会阻塞进程(所以需要一个单独的线程来等待客户端的连接),直到有一个客户连接建立后返回,它返回的是一个新可用的套接字,这个套接字就是连接套接字。
- 监听套接字:监听套接字正如accept()的参数sockfd,它就是监听套接字,在调用listen()函数之后。
- 连接套接字:一个套接字会从主动连接的套接字变为一个监听套接字;而accept()返回的是已连接socket描述字(一个连接套接字),它代表一个网络已经存在的点点连接。
- 一个服务器通常仅仅只创建一个监听socket描述符,它在该服务器的生命周期内一直存在。内核为每个有服务器进程接收的客户端连接创建了一个已连接的socket描述符,当服务器完成了对某个客户的服务后,相应的已连接socket描述符就被关闭。
- 连接套接字并没有占有新的端口与客户端通信,依然使用的是监听套接字一样的端口号。
- 6.recv()函数
- int recv( SOCKET s, char FAR *buf, int len, int flags );等级与WSARecv()
- 参数:s:Socket 的识别码
- buf:接收数据缓冲区
- len:缓冲区的长度
- flags:该参数影响该函数的行为,可以设为 0(表示无特殊行为),MSG_PEEK(会使有用的数据被复制到接收缓冲区内,但没有从系统缓冲区中将其删除),MSG_OOB(表示处理带外数据)
- 返回值:若成功则返回接收资料的长度,否则返回SOCKET_ERROR
- 7.send()函数
- int send( SOCKET s, const char FAR *buf,int len, int flags );等价于WSASend()
- 参数:s:Socket 的识别码
- buf:存放要传送数据的暂存区
- len:发送数据的长度
- flags:该参数影响该函数的行为,可以设为 0(表示无特殊行为),MSG_DONTROUTE(标志要求传输层不要将数据路由出去),MSG_OOB(表示该数据被带外发送)
- 返回值:若成功则返回发送的资料的长度,否则返回SOCKET_ERROR
- 8.closesocket()函数
- int closesocket(SOCKET s);关闭套接字,释放所占资源
- 返回值:成功返回0,否则返回SOCKET_ERROR
- 9.shutdown()函数
- int shutdown(SOCKET s, int how);用于通知对方不再发送数据,或者不再接收数据,或者既不发送也不接收数据
- 参数:s:套接字
- how:参数为SD_RECEIVE(表示不允许再调用接收数据函数);SE_SEND(表示不允许再调用发送数据函数);SD_BOTH(表示既不允许调用发送数据函数也不允许调用接收数据函数);
- 返回值:成功返回0,否则返回-1
- 10.connect()函数
- int connect(SOCKET s,const struct sockaddr FAR * name,int namelen);
- 参数:s:socket的标识码
- name:存储服务器的连接信息(协议族,服务器的ip,端口),服务器地址;
- namelen:sockaddr结构的长度
- 返回值:成功返回0,失败返回SOCKET_ERROR
- 11.字节转换函数
- 在网络上面有着许多类型的机器,这些机器在表示数据的字节顺序是不同的,比如i386芯片是低字节在内存地址的低端,高字节在高端,而alpha芯片却相反.为了统一起来,则有专门的字节转换函数.
- unsigned long int htonl(unsigned long int hostlong)
- unsigned short int htons(unisgned short int hostshort)
- unsigned long int ntohl(unsigned long int netlong)
- unsigned short int ntohs(unsigned short int netshort)
- 在这四个转换函数中,h代表host, n代表 network.s代表short l代表long
- 第一个函数的意义是将本机器上的long数据转化为网络上的long.其他几个函数的意义也差不多
- 12.地址结构说明
- typedef struct sockaddr_in
- {
- short sin_family;//由于我们主要使用Internet,所以一般为AF_INET
- u_short sin_port;//16位端口号,网络字节顺序
- struct in_addr sin_addr;//32位IP地址,网络字节顺序
- char sin_zero[8];//保留
- }SOCKADDR_IN;
- struct in_addr // Internet address.
- {
- uint32_t s_addr; // address in network byte order
- };
- //sin_:socket address internet
- struct sockaddr是通用的套接字地址,而struct sockaddr_in则是internet环境下套接字的地址形式,二者长度一样,都是16个字节。二者是并列结构,指向sockaddr_in结构的指针也可以指向sockaddr。一般情况下,需要把sockaddr_in结构强制转换成sockaddr结构再传入系统调用函数中。
- */
UDP程序
无连接协议的套接字调用时序
UDPServer
- // UDPServer.cpp : 定义控制台应用程序的入口点。
- //功能:接收客户端发来的"MyUDP"
- #include <stdio.h>
- #include <WinSock2.h>
- #pragma comment(lib,"ws2_32.lib")
- #define BUF_SIZE 64
- void main()
- {
- WSADATA wsd;
- if (WSAStartup(MAKEWORD(2,2),&wsd) != 0)//初始化套接字动态库
- {
- printf("WSAStartup failed,errno=%d\n",GetLastError());
- return;
- }
- SOCKET sockServ;
- if ((sockServ=socket(AF_INET,SOCK_DGRAM,0)) == INVALID_SOCKET)//创建套接字
- {
- printf("socket() failed,errno=%d\n",WSAGetLastError());
- WSACleanup();//释放套接字资源
- return;
- }
- int nBufLen;//接收数据缓冲区大小
- int nOptLen=sizeof(nBufLen);
- if (getsockopt(sockServ,SOL_SOCKET,SO_RCVBUF,(char*)&nBufLen,&nOptLen) == SOCKET_ERROR)//获取接收数据缓冲区大小
- {
- printf("getsockopt() failed,errno=%d\n",WSAGetLastError());
- }
- nBufLen *=10;//设置接收数据缓冲区为原来的10倍
- if (setsockopt(sockServ,SOL_SOCKET,SO_RCVBUF,(char*)&nBufLen,nOptLen) == SOCKET_ERROR)
- {
- printf("setsockopt() failed,errno=%d\n",WSAGetLastError());
- }
- int newRcvBuf;//检查设置系统接收数据缓冲区是否成功
- if (getsockopt(sockServ,SOL_SOCKET,SO_RCVBUF,(char*)&newRcvBuf,&nOptLen) == SOCKET_ERROR)
- {
- printf("second getsockopt() failed,errno=%d\n",WSAGetLastError());
- }
- if (newRcvBuf != nBufLen)
- {
- printf("set bufLen size=size*10 failed\n");
- }
- //设置服务器地址
- SOCKADDR_IN addrServ;
- addrServ.sin_family=AF_INET;
- addrServ.sin_addr.s_addr=INADDR_ANY;
- addrServ.sin_port=htons(5000);
- if (bind(sockServ,(SOCKADDR*)&addrServ,sizeof(addrServ)) == SOCKET_ERROR)//绑定
- {
- printf("bind() failed,errno=%d\n",WSAGetLastError());
- closesocket(sockServ);
- WSACleanup();
- return;
- }
- //接收数据
- SOCKADDR_IN addrClient;
- int addrClientLen=sizeof(addrClient);
- char buf[BUF_SIZE];
- ZeroMemory(buf,BUF_SIZE);
- if (recvfrom(sockServ,buf,BUF_SIZE,0,(SOCKADDR*)&addrClient,&addrClientLen) == SOCKET_ERROR)
- {
- printf("recvfrom() failed,errno=%d\n",WSAGetLastError());
- closesocket(sockServ);//关闭套接字
- WSACleanup();
- return;
- }
- printf("%s\n",buf);//输出 MyUDP
- closesocket(sockServ);
- WSACleanup();
- system("pause");
- return;
- }
- /*
- UDP编程的服务器一般步骤是
- 1.创建一个socket,用函数socket();
- 2.设置socket属性,用函数setsockopt(); //可选
- 3.绑定IP地址,端口灯属性到socket上,用函数bind();
- 4.循环接收数据,用函数recvfrom();
- 5.关闭网络连接;
- */
UDPClient
- // UDPClient.cpp : 定义控制台应用程序的入口点。
- //功能:向服务器端发送"MyUDP"
- #include <stdio.h>
- #include <WinSock2.h>
- #pragma comment(lib,"ws2_32.lib")
- #define BUF_SIZE 64
- void main()
- {
- WSADATA wsd;
- if (WSAStartup(MAKEWORD(2,2),&wsd) != 0)//初始化套接字动态库
- {
- printf("WSAStartup() failed,errno=%d\n",GetLastError());
- return;
- }
- SOCKET sClient;
- if ((sClient=socket(AF_INET,SOCK_DGRAM,0)) == INVALID_SOCKET)//创建套接字
- {
- printf("socket() failed,errno=%d\n",WSAGetLastError());
- WSACleanup();//释放套接字资源
- return;
- }
- char buf[BUF_SIZE];
- ZeroMemory(buf,BUF_SIZE);
- strcpy(buf,"MyUDP");
- //服务器地址
- SOCKADDR_IN addrServ;
- int addrServLen=sizeof(addrServ);
- addrServ.sin_family=AF_INET;
- addrServ.sin_addr.s_addr=inet_addr("127.0.0.1");
- addrServ.sin_port=htons(5000);
- if (sendto(sClient,buf,BUF_SIZE,0,(SOCKADDR*)&addrServ,addrServLen) == SOCKET_ERROR)//发送数据
- {
- printf("sendto() failed,errno=%d\n",WSAGetLastError());
- closesocket(sClient);//关闭套接字
- WSACleanup();
- return;
- }
- closesocket(sClient);
- WSACleanup();
- system("pause");
- return;
- }
- /*
- UDP编程的客户端的一般步骤是
- 1.创建一个socket,用函数socket();
- 2.设置socket属性,用函数setsockopt(); //可选
- 3.绑定IP地址,端口等信息到socket上,用函数bing(); //可选
- 4.设置对方的IP地址和端口等属性;
- 5.发送数据,用sendto();
- 6.关闭网络连接;
- */
UDP API
- /*
- 1.recvfrom()函数
- int recvfrom(SOCKET s,char FAR* buf,int len,int flags,struct sockaddr FAR* from,int FAR* fromlen);用于接收数据,并且返回发送数据主机地址
- buf:接收数据缓冲区
- len:接收数据缓冲区的大小
- flags:该参数影响recvfrom()函数的行为。参数可以为0(表示无特殊行为);MSG_PEEK(会使有用的数据被复制到接收缓冲区内,但没有从系统缓冲区中将其删除);MSG_OOB(表示处理带外数据);
- from:该参数返回发送数据主机的地址
- fromlen:地址长度
- 返回值:成功返回接收数据的字节数,否则返回SOCKET_ERROR
- 2.sendto()函数
- int sendto(SOCKET s,const char FAR* buf,int len,int flags,const struct sockaddr FAR* to,int tolen);//发送数据
- s:套接字
- buf:发送数据的缓冲区
- len:发送数据缓冲区的大小
- flags:该参数影响sendto()函数的行为。参数可以为0(表示无特殊行为);MSG_DONTROUTE(标志要求传输层不要将数据路由出去);MSG_OOB(标志预示该数据应该被带外发送);
- to:接收数据的地址
- tolen:地址长度
- 返回值:成功则返回发送数据的字节数,否则返回SOCKET_ERROR
- */
- /*
- 1.getsockopt()函数
- int getsockopt(SOCKET s,int level,int optname,char FAR* optval,int FAR* optlen);用于获取套接字选项信息
- s:套接字
- level:选项级别,有SOL_SOCKET,IPPROTO_TCP两个级别
- optname:套接字选项名称
- optval:接收数据缓冲区,该参数返回套接字选项名称对应的值
- optlen:缓冲区大小
- 返回值:成功返回0,否则返回SOCKET_ERROR
- 2.setsockopt()函数
- int setsockopt(SOCKET s,int level,int optname,const char FAR* optval,int optlen);设置套接字选项
- s:套接字
- level:选项级别:有SOL_SOCKET和IPPROTO_TCP两个级别
- optname:套接字选项名称
- optval:该参数用于设置套接字选项的值
- optlen:缓冲区大小
- 返回值:成功返回0,否则返回SOCKET_ERROR
- */