1 TCP/IP协议与Winsock网络编程接口
为了方便网络编程,20世纪90年代初,Microsoft联合其他几家公司共同制定了一-套Windows下的网络编程接口,即Winsock规范。它不是一种网络协议,而是-一套开放的、支持多种协议的Windows下的网络编程接口。Winsock可以访问很多种网络协议,可以把它当作﹒些协议的封装。现在的Winsock已经基本上实现了与协议无关。可以使用Winsock束调用多种协议的功能。
那么,Winsock和TCP/IP协议到底有什么关系呢?实际上,Winsock就是TCP/IP协议的种封装。你可以通过调用Winsock的接口函数来调用TCP/IP的各种功能。例如,我们想用TCP/IP协议发送数据,就可以使用Winsock的接口函数send()来调用TCP/IP的发送数据功能,Winsock已经封装好了发送数据的功能。
2 Winsock 的常用API
2.1 WSAStartup函数
WSAStartup函数的格式如下
1
| int WSastartup( WORD wversionRequested,LPWSADATA 1pwSAData);
|
程序在使用Socket之前必须调用WSAStartup函数。
该函数执行成功后返回0。
2.2 socket函数
socket函数的格式为:
1
| Socket socket( int af, int type, int protocol );
|
应用程序调用socket函数来创建一个能够进行网络通信的套接字。
第一个参数指定应用程序使用的通信协议的协议族,如果使用TCP/IP协议族,则该参数置PF_INET,设置通信域(本地(PF_LOCAL),ipv4(PF_INET),ipv6(PF_INET6)。
第二个参数指定要创建的套接字类型(流套接字类型为SOCK_STREAM,数据报套接字类型为SOCK_DGRAM)。
第三个参数指定应用程序所使用的通信协议,常用的有 IPPROTO_TCP 和 IPPTOTO_UDP,分别表示 TCP 传输协议和 UDP 传输协议。
如果该函数调用成功,则返回新创建的套接字的描述符,如果失败则返回INVALID_SOCKET。套接字描述符是一个整数类型的值。每个进程的进程空间里都有一个套接字描述符表,该表中存放着套接字描述符和套接字数据结构的对应关系。该表用一个字段存放新创建的套接字的描述符,另一个字段存放套接字数据结构的地址,因此根据套接字描述符就可以找到其对应的套接字数据结构。
2.3 bind函数
bind函数的格式为:
1
| int bind( SOCKET s,const struct sockaddr FAR *name, int namelen);
|
当创建了一个Socket以后,套接字数据结构中有一个默认的IP地址和默认的端口号。一个服务程序必须调用bind函数来给Socket绑定一个IP地址和一个特定的端口号。客户程序一般不必调用bind函数来为其Socket绑定IP地址和端口号。该函数的第一个参数指定待绑定的Socket描述符;第二个参数指定一个sockaddr结构,该结构的定义如下:
1 2 3 4 5 6
| struct sockaddr { u _short sa_family; char sa_data[ 14 ]; };
|
sa_family指定地址族,对于TCP/IP协议族的套接字,将其置AF_INET。当对TCP/IP协议族的套接字进行绑定时,我们通常使用如下所示的另一个地址结构:
1 2 3 4 5 6 7 8 9 10
| struct sockaddr_in{
sa_family_t sin_family;
uint16_t sin_port;
struct in_addr sin_addr;
char sin_zero[8]; };
|
其中,sin_family置AF_INET; sin_port指明端口号; sin_addr结构体中只有一个唯一的字段s_addr,它表示IP地址。该字段是一个整数,一般用函数inet_addr()把字符串形式的IP地址转换成无符号长整型的整数值后再赋给s_addr。我们用0来填充sin_zero数组,目的是让sockaddr_in结构的大小与sockaddr结构的大小一致。
1 2 3
| struct in_addr{ in_addr_t s_addr; };
|
s_addr 是一个整数,而IP地址是一个字符串,所以需要 inet_addr() 函数进行转换将一个无符号短整型数值转换为网络字节序,即大端模式。
2.4 recvfrom函数
recefrom为无连接读函数,函数格式为:
1
| int recvfrom(int sockfd, void *buf, int buf_len, unsigned int flags,struct sockaddr *from,int fromlen);
|
从UDP接收数据,返回实际接收的字节数,失败时返回-1
Sockfd:套接字描述符
buf:指向内存块的指针
buf_len:内存块大小,以字节为单位
flags:一般为0
from:远端的地址,IP地址和端口号
fromlen:远端地址长度
举例:recvfrom(sockfd,buf,8192,0,(struct sockaddr *)&address, sizeof(address));
2.5 sendto函数
sendto为无连接函数,函数格式为:
1
| int sendto(int sockfd, const void * data, int data_len, unsigned int flags, struct sockaddr *remaddr,int remaddr_len)
|
基于UDP发送数据报,返回实际发送的数据长度,出错时返回-1
sockfd:套接字描述符
data:指向要发送数据的指针
data_len:数据长度
flags:通常为0
remaddr:远端地址:IP地址和端口号
remaddr_len :地址长度
举例:sendto(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&address, sizeof(address));
2.6 closesocket函数
closesocket函数的格式为
1
| int closesocket ( SOCKET s ) ;
|
closesocket函数用来关闭一个描述符为s的套接字。每个进程中都有一个套接字描述符表,表中的每个套接字描述符都与一个位于操作系统缓冲区中的套接字数据结构相对应,有可能有几个套接字描述符指向同一个套接字数据结构。套接字数据结构中专门有一个字段存放该结构被引用的次数,即有多少个套接字描述符指向该结构。closesocket函数如果执行成功就返回0,否则返回SOCKET_ERROR。
2.7 WSACleanup函数
WSACleanup函数的格式为
1
| int WSAcleanup ( void ) ;
|
应用程序在完成对请求的Socket库的使用后,要调用WSACleanup函数来解除与Socket库的绑定并且释放Socket库所占用的系统资源。
3 代码实现
3.1 服务端代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
| #include<stdio.h> #include<winsock2.h>
#define BUF_SIZE 255
int main(int argc,char *argv[]) {
WORD socketVersion=MAKEWORD(2,2); WSADATA wsadata; SOCKET serveSocket; struct sockaddr_in serveAddr; struct sockaddr_in clientAddr; int len=-1; int addrLen=sizeof(clientAddr); char receiveData[BUF_SIZE]; char sendData[BUF_SIZE]; if(WSAStartup(socketVersion,&wsadata)!=0) { printf("socket库初始化失败\n"); return 0; } serveSocket=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP); if(serveSocket==INVALID_SOCKET) { printf("socket服务器创建失败"); return 0; } serveAddr.sin_family=AF_INET; serveAddr.sin_port=htons(15632); serveAddr.sin_addr.S_un.S_addr= htonl(INADDR_ANY); if(bind(serveSocket,(SOCKADDR*)&serveAddr,sizeof(serveAddr))==SOCKET_ERROR) { printf("绑定IP和端口出现问题\n"); return 0; } printf("服务器开始运行\n"); while(true) { len=recvfrom(serveSocket,receiveData,BUF_SIZE-1,0,(SOCKADDR*)&clientAddr,&addrLen); if(len>0) { receiveData[len]=0x00; printf("接收到一个连接,IP地址为:%s \n",inet_ntoa(clientAddr.sin_addr)); printf("客户端:%s \n",receiveData); } printf("服务器:"); fgets(sendData,sizeof(sendData),stdin); sendData[strlen(sendData)-1]='\0'; sendto(serveSocket,sendData,strlen(sendData),0,(SOCKADDR*)&clientAddr,addrLen); } closesocket(serveSocket); WSACleanup(); return 0; }
|
3.2 客户端代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
| #include<stdio.h> #include<winsock2.h>
#define BUF_SIZE 255
int main(int argc,char *argv[]) {
WORD socketVersion=MAKEWORD(2,2); WSADATA wsadata; SOCKET clientSocket; struct sockaddr_in serveAddr; int len=-1; int addrLen=sizeof(serveAddr);
char receiveData[BUF_SIZE]; char sendData[BUF_SIZE]; if(WSAStartup(socketVersion,&wsadata)!=0) { printf("socket库初始化失败\n"); return 0; } clientSocket=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP); if(clientSocket==INVALID_SOCKET) { printf("socket客户端创建失败\n"); return 0; } if(argc==5) { struct sockaddr_in clientAddr; clientAddr.sin_family=AF_INET; clientAddr.sin_addr.S_un.S_addr=inet_addr(argv[2]); clientAddr.sin_port=htons(atoi(argv[3])); if(bind(clientSocket,(SOCKADDR*)&clientAddr,sizeof(clientAddr))==SOCKET_ERROR) { printf("绑定IP和端口出现问题\n"); return 0; } serveAddr.sin_family=AF_INET; serveAddr.sin_port=htons(atoi(argv[5])); serveAddr.sin_addr.S_un.S_addr=inet_addr(argv[4]); } else { serveAddr.sin_family=AF_INET; serveAddr.sin_port=htons(15632); serveAddr.sin_addr.S_un.S_addr=inet_addr("127.0.0.1"); }
while(true) { printf("客户端:"); fgets(sendData,sizeof(sendData),stdin); sendData[strlen(sendData)-1]='\0'; if(strcmp(sendData,"bye") == 0) break; printf("send OK!\n");
sendto(clientSocket,sendData,strlen(sendData),0,(SOCKADDR*)&serveAddr,addrLen); len=recvfrom(clientSocket,receiveData,BUF_SIZE-1,0,(SOCKADDR*)&serveAddr,&addrLen); if(len>0) { receiveData[len]=0x00; printf("服务器:%s \n",receiveData); } } closesocket(clientSocket); WSACleanup(); return 0;
}
|
4 遇到的问题
4.1 undefined reference to’WSAStartup’相关编译错误
打开dev-C++的工具->编译选项,添加-lws2_32命令即可解决,如下图所示。
参考资料
基于UDP的服务器端和客户端 (biancheng.net)
Dev C++的undefined reference to ‘__imp_htons’或codeclock的undefined reference to’WSAStartup to@8’相关编译器错