服务器端程序流程
服务器端的功能在指定的端口上监听,等待客户端的连接。在连接建立后可使用send()、recv()发送、接收数据。一般情况下,socket程序服务端过程如下
- 程序在运行后,首先需要调用WSAStartup()加载ws2_32.dll
- 调用socket()创建用于监听的SOCKET,在创建时需要指定使用的网络协议,连接类型等
- 调用bind()将SOCKET绑定到网络地址和端口
- 调用listen()开始监听
- 调用accept()等待客户端连接。在客户端连接后,accept()返回,得到连接SOCKET。在accept()返回后,可立即在调用,以处理其他客户端的连接
- 得到连接SCOKET后,可调用send()和recv()发生接收数据
- 在数据传输完成后,可调用closesocket()关闭SOCKET
- 调用WSACleanup()释放ws2_32.dll
面向连接的应用程序流程图
服务器程序
新建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描述符就被关闭。
- 连接套接字并没有占有新的端口与客户端通信,依然使用的是监听套接字一样的端口号。
- */
问题:当有多个客户端并发连接服务器端时,上程序会怎样,能处理吗?答案是不能的;需要添加一个监听线程。
此文章来自于【http://blog.csdn.net/ouyangshima/article/details/8932334】