服务器端的功能在指定的端口上监听,等待客户端的连接。在连接建立后可使用send()、recv()发送、接收数据。一般情况下,socket程序服务端过程如下
新建win32项目控制台程序 Win32Server项目:
// Win32Server.cpp : 定义控制台应用程序的入口点。 #include "stdafx.h" #include <WinSock2.h> #include <ws2tcpip.h> #pragma comment(lib,"wsock32.lib") #pragma comment(lib,"ws2_32.lib") #define DEFPORT "10000" #define DEFPORTTWO 10000 //处理接收的消息 void HandleMsg(SOCKET sockfd,char* msg) { int nSend=0; char sendBuf[2048]={0}; if (lstrcmpiA(msg,"download") == 0) { strcpy(sendBuf,"we get downLoad\n"); } else if (lstrcmpiA(msg,"get information") == 0) { strcpy(sendBuf,"we get information!!!!\n"); } nSend=send(sockfd,sendBuf,strlen(sendBuf),0); if (nSend == SOCKET_ERROR) { printf("error at send(),threadID=%d, errno=%d\n",sockfd,WSAGetLastError()); closesocket(sockfd); } printf("sockID=%d,send client [%d]bytes----%s",sockfd,nSend,sendBuf); } //连接线程 DWORD WINAPI ConnectionThread(LPVOID lpParam) { DWORD dwThreadID=GetCurrentProcessId(); SOCKET sockfd=(SOCKET)lpParam; int recvByte=0; char recvBuf[2096]; recvByte=recv(sockfd,recvBuf,strlen(recvBuf)+1,0); if (recvByte == 0)//接收数据失败,连接关闭 { printf("接收数据失败,连接关闭!!!"); closesocket(sockfd); return 0; } else if (recvByte == SOCKET_ERROR)//接收数据失败,socket错误 { printf("error at recv,erron=%d\n",WSAGetLastError()); closesocket(sockfd); return 0; } else if (recvByte > 0) { printf("ConnectionThread(%d),[%d] Bytes received:%s\n",dwThreadID,recvByte,recvBuf); HandleMsg(sockfd,recvBuf); } closesocket(sockfd); return 0; } void main() { WSADATA data; if(WSAStartup(MAKEWORD(2,2),&data) != NO_ERROR) { printf("error at WSAStartup,errno=%d\n",GetLastError()); return; } SOCKET listenSocket=INVALID_SOCKET; if((listenSocket=socket(AF_INET,SOCK_STREAM,0)) == INVALID_SOCKET) { printf("error at socket(),errno=%d\n",WSAGetLastError()); WSACleanup(); return; } printf("socket successfully!!!\n"); // SOCKADDR_IN addr; // addr.sin_family=AF_INET; // addr.sin_addr.S_un.S_addr=htonl(INADDR_ANY); // addr.sin_port=htons(DEFPORTTWO); // if (bind(listenSocket,(SOCKADDR*)&addr,sizeof(addr)) == SOCKET_ERROR) // { // printf("error at bind(),errno=%d\n",WSAGetLastError()); // closesocket(listenSocket); // WSACleanup(); // return; // } //等价于下面的代码 addrinfo *result=NULL,hints; ZeroMemory(&hints,sizeof(hints)); SOCKADDR_IN dd; hints.ai_family=AF_INET; hints.ai_socktype=SOCK_STREAM; hints.ai_socktype=0; hints.ai_flags=AI_PASSIVE; if(getaddrinfo(NULL,DEFPORT,&hints,&result) != 0) { printf("error at getaddrinfo\n"); closesocket(listenSocket); WSACleanup(); return; } if (bind(listenSocket,result->ai_addr,result->ai_addrlen) == SOCKET_ERROR) { printf("error at bind(),errno=%d\n",WSAGetLastError()); freeaddrinfo(result); closesocket(listenSocket); WSACleanup(); return; } freeaddrinfo(result);//result不再需要 printf("bind successfully!!!\n"); if (listen(listenSocket,SOMAXCONN) == SOCKET_ERROR) { printf("error at listen(),errno=%d\n",WSAGetLastError()); closesocket(listenSocket); WSACleanup(); return; } printf("listen successfully!!!\n"); SOCKET clientSocket=INVALID_SOCKET; SOCKADDR_IN clientAddr; int clientAddrLen=sizeof(clientAddr); while(1) { printf("ready to accept\n"); clientSocket=accept(listenSocket,(SOCKADDR*)&clientAddr,&clientAddrLen); if(clientSocket == INVALID_SOCKET) { printf("error at accept(),errno=%d\n",WSAGetLastError()); closesocket(listenSocket); break;//等待连接错误,退出循环 } //printf("accept a connetion[%d.%d.%d.%d]:%d,sockfd=%d\n\n\n\n",clientAddr.sin_addr.s_net,clientAddr.sin_addr.s_host,clientAddr.sin_addr.s_lh,clientAddr.sin_addr.s_impno,ntohs(clientAddr.sin_port),clientSocket);//等价于下一行 printf("accept a connetion[%s]:%d,sockfd=%d\n\n\n",inet_ntoa(clientAddr.sin_addr),ntohs(clientAddr.sin_port),clientSocket); //为每一个连接创建一个数据发送的接收线程,使服务端又可以立即接收其他的客户端连接 if (!CreateThread(NULL,0,ConnectionThread,(LPVOID)clientSocket,0,NULL)) { printf("create thread error(%d)\n",GetLastError()); break; } } WSACleanup(); }
/* 当我们调用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:name的长度 返回值:成功返回0,否则返回SOCKET_ERROR 通常服务器在启动的时候会绑定一个众说周知的地址(如ip地址+端口号),用于提供服务,客户就可以通过它(ip+port)来连接服务器;而客户端就不用指定,有系统自动分配一个端口号和自身的ip地址组合。这就是为什么通常服务器端在listen之前会调用bind(),而客户端就不用调用,而是在connect()时由系统随机生成一个。 int listen(SOCKET s,int backlog); 参 数: s:一个已绑定的套接字 backlog:连接请求队列的最大长度(一般2~4,用SOMAXCONN则有系统确定)。socket可以排队的最大连接个数,不是最多可以连接几个客户端。更具体些:TCP模块允许的已完成三次握手过程(TCP模块完成)但还没来得及被应用程序accep()的最大连接数。 返回值:成功返回0,否则返回SOCKET_ERROR socket()函数创建的socket默认是一个主动类型的,listen()函数则将主动连接套接口socket变为被动连接套接口,使得这个进程可以接受其他进程的请求(客户的连接请求),从而成为一个服务器进程。 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描述符就被关闭。 连接套接字并没有占有新的端口与客户端通信,依然使用的是监听套接字一样的端口号。 */运行结果: