一、socket通信过程简介
在WIN32平台上的WINSOCK编程都要经过下列步骤:
定义变量->获得WINDOCK版本->加载WINSOCK库->初始化->创建套接字->设置套接字选项->关闭套接字->卸载WINSOCK库->释放资源。
在VC中进行WINSOCK编程时,需要引入如下两个库文件:WINSOCK.H(这个是WINSOCK API的头文件,WIN2K以上支持WINSOCK2,所以WINSOCK2.H);Ws2_32.lib(WINSOCK API连接库文件)。
使用方式如下:
#include <winsock.h>
#pragma comment(lib,"ws2_32.lib")
二、重要函数
1.加载winsock文件
/*加载winsock文件*/ WSADATA wsaData; //WSADATA结构被用来储存调用AfxSocketInit全局函数返回的Windows Sockets初始化信息 定义在Winsock.h WORD sockVersion=MAKEWORD(2,0); //使用WINSOCK2版本 ::WSAStartup(sockVersion,&wsaData); //第一个参数是WINSOCK 版本号,第二个参数是指向WSADATA的指针. //该函数返回一个INT型值,通过检查这个值来确定初始化是否成功
/*创建服务器端的套接字*/ SOCKET s=::socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); //三个参数分别代表 使用TCP/IP;传输过程使用TCP;不适用其他特殊协议 if(s==INVALID_SOCKET) { printf("Failed socket()\n"); ::WSACleanup(); system("pause"); return 0; }
(服务器端)
/*socket中装入地址信息*/ sockaddr_in sin; sin.sin_family=AF_INET; //sin_family指代协议族,在socket编程中只能是AF_INET sin.sin_port=htons(13); //表示服务器监听的端口号为13 sin.sin_addr.S_un.S_addr=INADDR_ANY; //存储IP地址,INADDR_ANY就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址,或“所有地址”、“任意地址”
/*socket中装入地址信息*/ sockaddr_in servAddr; servAddr.sin_family=AF_INET; servAddr.sin_port=htons(13); /*接收服务器13端口号*/ servAddr.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");/*本地IP地址为127.0.0.1*/
/*绑定地址及端口号*/ if(::bind(s,(LPSOCKADDR)&sin,sizeof(sin))==SOCKET_ERROR)//返回:0---成功,-1---失败 { printf("Failed bind()\n"); ::WSACleanup(); system("pause"); return 0; }
参数一:指定与那个套接字绑定;
参数二:指定地址;
参数三:确定复制多少数据。
bind函数并不是总是需要调用的,只有用户进程想与一个具体的地址或端口相关联的时候才需要调用这个函数。如果用户进程没有这个需要,那么程序可以依赖内核的自动的选址机制来完成自动地址选择,而不需要调用bind的函数,同时也避免不必要的复杂度。
在一般情况下,对于服务器进程问题需要调用bind函数,对于客户进程则不需要调用bind函数。
5.监听客户端的连接请求(服务器端调用)
/*监听客户端的连接请求*/ if(::listen(s,2)==SOCKET_ERROR)//返回:0---成功,-1---失败 { printf("Failed listen()\n"); ::WSACleanup(); system("pause"); return 0; }
参数一:被listen函数调用的套接字;
参数二:连接数量的上限。
在服务器编程中,用户希望这个套接字可以接受外来的连接请求,也就是被动等待用户来连接。由于系统默认时认为一个套接字是主动连接的,所以需要通过某种方式来告诉系统,用户进程通过系统调用listen来完成这件事。
6.等待并接受连接(服务器端调用)
client=::accept(s,(SOCKADDR*)&remoteAddr,&nAddrLen);//accept默认会阻塞进程,直到有一个客户连接建立后返回,它返回的是一个新可用的套接字,这个套接字是连接套接字。 if(client==INVALID_SOCKET) { printf("Failed accept()\n"); continue; }
参数一:监听套接字,这个套接字用来监听一个端口,当有一个客户与服务器连接时,它使用这个一个端口号,而此时这个端口号正与这个套接字关联。当然客户不知道套接字这些细节,它只知道一个地址和一个端口号。
参数二:一个结果参数,它用来接受一个返回值,这返回值指定客户端的地址。
7.连接服务器(客户端调用)
/*连接服务器*/ if(::connect(s,(sockaddr*)&servAddr,sizeof(servAddr))==-1)//返回:0---成功,-1---失败 { printf("Failed connect()\n"); ::WSACleanup(); system("pause"); }
面向连接的协议,在建立连接的时候总会有一方先发送数据,那么谁调用了connect谁就是先发送数据的一方。
参数一:指定数据发送的套接字;
参数二:指定数据发送的目的地址,也就是服务器端的地址。
一个简单的例子:
(服务器端)
/* 服务器端 */ #include<winsock2.h> #include<stdio.h> #include<windows.h> #include<time.h> #pragma comment(lib,"WS2_32.lib") int main(int argc,char*argv[]) { /*加载winsock文件*/ WSADATA wsaData; //WSADATA结构被用来储存调用AfxSocketInit全局函数返回的Windows Sockets初始化信息 定义在Winsock.h WORD sockVersion=MAKEWORD(2,0); //使用WINSOCK2版本 ::WSAStartup(sockVersion,&wsaData); //第一个参数是WINSOCK 版本号,第二个参数是指向WSADATA的指针. //该函数返回一个INT型值,通过检查这个值来确定初始化是否成功 /*创建服务器端的套接字*/ SOCKET s=::socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); //三个参数分别代表 使用TCP/IP;传输过程使用TCP;不适用其他特殊协议 if(s==INVALID_SOCKET) { printf("Failed socket()\n"); ::WSACleanup(); system("pause"); return 0; } /*socket中装入地址信息*/ sockaddr_in sin; sin.sin_family=AF_INET; //sin_family指代协议族,在socket编程中只能是AF_INET sin.sin_port=htons(13); //表示服务器监听的端口号为13 sin.sin_addr.S_un.S_addr=INADDR_ANY; //存储IP地址,INADDR_ANY就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址,或“所有地址”、“任意地址”。 /*绑定地址及端口号*/ if(::bind(s,(LPSOCKADDR)&sin,sizeof(sin))==SOCKET_ERROR) { printf("Failed bind()\n"); ::WSACleanup(); system("pause"); return 0; } /*监听客户端的连接请求*/ if(::listen(s,2)==SOCKET_ERROR) { printf("Failed listen()\n"); ::WSACleanup(); system("pause"); return 0; } sockaddr_in remoteAddr; int nAddrLen=sizeof(remoteAddr); SOCKET client; time_t t = time( 0 ); char tmp[64]; strftime( tmp, sizeof(tmp), "%Y/%m/%d %X %A\n\t", localtime(&t) );//提取系统时间 /*循环接受连接请求*/ while(TRUE) { client=::accept(s,(SOCKADDR*)&remoteAddr,&nAddrLen);//accept默认会阻塞进程,直到有一个客户连接建立后返回,它返回的是一个新可用的套接字,这个套接字是连接套接字。 if(client==INVALID_SOCKET) { printf("Failed accept()\n"); continue; } printf("接收到一个客户端的连接:%s\r\n\n等待下一个客户端的连接……\n\n",inet_ntoa(remoteAddr.sin_addr)); ::send(client,tmp,strlen(tmp),0); /*发送本地时间给客户端*/ ::closesocket(client); /*关闭连接*/ } ::closesocket(s);/*关闭套接字*/ ::WSACleanup(); system("pause"); return 0; }
/* 客户端 */ #include<winsock2.h> #include<stdio.h> #include<windows.h> #pragma comment(lib,"WS2_32.lib") int main(int argc,char*argv[]) { /*加载winsock文件*/ WSADATA wsaData; WORD sockVersion=MAKEWORD(2,0); ::WSAStartup(sockVersion,&wsaData); /*创建服务器端的套接字*/ SOCKET s=::socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); if(s==INVALID_SOCKET) { printf("Failed socket()\n"); ::WSACleanup(); system("pause"); } /*socket中装入地址信息*/ sockaddr_in servAddr; servAddr.sin_family=AF_INET; servAddr.sin_port=htons(13); /*接收服务器13端口号*/ servAddr.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");/*本地IP地址为127.0.0.1*/ /*连接服务器*/ if(::connect(s,(sockaddr*)&servAddr,sizeof(servAddr))==-1) { printf("Failed connect()\n"); ::WSACleanup(); system("pause"); } /*接收数据并打印到屏幕上*/ char buff[256]; int nRecv=::recv(s,buff,256,0); if(nRecv>0) { buff[nRecv]='\0'; printf("连接成功\n"); printf("接收到数据:%s\n",buff); system("pause"); } return 0; }