预备:
加载套接字库。
过程:
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.Create Socket!
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.Bind Socket to a port!
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_da
};
其中sa_family指定地址家族,对于TCP/IP协议的套接字,必须设置为AF_INET,sa_da
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正好相反。