VC++中Socket编程的实现---【TCP服务器端】

    技术2026-08-14  1

    Socket在TCP服务器端

    预备:

    加载套接字库。

    过程:

    1.创建套接字(socket)。 2.将套接字绑定到一个本地地址和端口上(bind)。 3.将套接字设为监听模式,准备接受客户请求(listen)。 4.等待客户请求到来;当请求到来后,接受连接请求,返回一个新对应于此次连接的套接字(accept)。 5.用返回的套接字和客户端进行通讯(send/recv)。 6.返回等待另一客户请求。 7.关闭套接字。

    实现:

    //加载套接字库

    #include <Winsock2.h> #include <stdio.h> //还需要添加链接库ws2_32.lib(工程=设置=链接=对象/库模块) WORD wVersionRequested; //准备加载Winsock库的版本,注意高字节是副版本号 WSADATA wsaData; //是一个返回值,指向WSADATA结构的指针,WSAStartup函数将其加载的库版本信息输入到这个结构体中。 int err; wVersionRequested = MAKEWORD(1,1); err = WSAStartup(wVersionRequested,&wsaData); if(0 != err){ return; } if(LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1){ WSACleanup(); //如果加载的版本不正确,返回。需要注意的是高字节是副版本号,低字节是主版本号 return; }

    //跟着步骤实现套接字

    1.创建套接字

    socket(int af,int type,int protocol);    //socket 函数三个参数af,指定地址族,对于TCP/IP协议套接字,它只能是AF_INET;type参数指定Socket类型,对于1.1版本的Socket,它只支持两种类型的套接字,SOCK_STREAM指定产生流失套接字,SOCK_DGRAM产生数据报套接字;protocol是与特定的地址家族相关的协议,如果指定为0,那么系统就会根据地址格式和套接字类别,自动选择一个合适的协议。

    SOCKET sockSrv = socket(AF_INET,SOCK_STREAM,0);

    2.绑定一个端口到套接字上

    SOCKADDR_IN addrSrv; addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY); addrSrv.sin_family=AF_INET; addrSrv.sin_port=htons(6000); bind(sockSrv,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR)); int bind(SOCKET s,const struct sockaddr FAR *name,int namelen);

    //s为指定要绑定的套接字;name指定了该套接字的本地地址信息,这是一个指向sockaddr结构的指针变量,该地址结构是为所有的地址家族准备的,这个结构可能随时用的网络协议不同而不同,所以才需要第三个参数namelen指定该地址结构的长度。

    sockaddr结构的定义如下:

    struct sockaddr{ u_short sa_family; char sa_data[14]; };

    其中sa_family指定地址家族,对于TCP/IP协议的套接字,必须设置为AF_INET,sa_data[14]只是一个占位的作用,该区域中指定与协议相关的具体地址信息。由于实际要求只是内存区,所以对不同的协议家族,用不同的结构来替换sockaddr。除了sa_family外,sockadd是按网络字节顺序表示的。在基于TCP/IP的socket编程中,可以使用sockaddr_in结构替换sockaddr,以方便我们填写地址信息。

    struct sockaddr_in{ short sin_family; unsigned short sin_port; //Port 2 Bytes! struct in_addr sin_addr; //Address 4 Bytes! char sin_zero[8]; //8 Bytes! };

    sockaddr_in和sockaddr的长度是一样的。

    需要注意的是sin_addr的类型是in_addr,而in_addr实际上是一个联合

    struct in_addr{ union{ struct{u_char s_b1,s_b2,s_b3,s_b4;}S_un_b; struct{u_short s_w1,s_w2;}S_un_w; u_long S_addr; }S_un; };

    所以才有了上面的  addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);

    INADDR_ANY,表示的是所有IP!

    3.将套接字设为监听模式

    listen(sockSrv,5);

    // int listen(SOCKET s,int backlog); s是Socket不用说,backlog是等待队列的最大长度,比如现在设为2,那么假如同时有3个请求来到服务器端的时候,就把前两个放到请求队列中,第三个就会被拒绝。

    4.等待客户请求到来,返回连接套接字和通讯

    SOCKADDR_IN addrClient; int len=sizeof(SOCKADDR); while(1) { SOCKET sockConn=accept(sockSrv,(SOCKADDR*)&addrClient,&len); //建立连接,返回对应于此次连接的SOCKET char sendBuf[100]; //发送数据的buff sprintf(sendBuf,"Welcome %s to my space!", inet_ntoa(addrClient.sin_addr)); //格式化客户端地址 send(sockConn,sendBuf,strlen(sendBuf)+1,0); //发送的字节多一个,最后一个是空,结束! char recvBuf[100]; //接收数据的buff recv(sockConn,recvBuf,100,0); //接收数据 printf("%s/n",recvBuf); closesocket(sockConn); //关闭连接 }

    //accept();这个函数是用来接受客户端发送的连接请求,并返回建立连接的Socket:

    SOCKET accept( SOCKET s, struct sockaddr FAR* addr, int FAR* adrlen );

    s表示已经通过listen函数设为监听状态的SOCKET!addr是指向缓冲区的指针,保存发起连接的这个客户端的IP地址信息和端口信息;addrlen是addr的长度。

    //send函数通过一个已经建立连接的套接字发送数据,recv是通过一个已经建立连接的套接字接收数据。两个函数的参数基本一样。

    int send(SOCKET s,const char FAR* buf,int len,int flags);

    不同的只有buf,对于send那是发送数据的buf,而对于recv那是接收数据的buf,flags通常情况下为0.

    注意上面用了一个函数inet_ntoa是格式化IP地址的,将接受一个ULONG型的数据返回一个被格式化成了点分的字符串的IP(如:192.168.0.1),还有一个inet_addr作用和inet_ntoa正好相反。

    最新回复(0)