所谓server就是同时响应多个客户端的请求的软件,这里先给出server/client编程的框架,这个表里的函数如何使用,是网络编程的核心,也是本文的重点
这里有个关键的名词socket套接字.是网络编程中的概念,对TCP/IP协议进行了抽象和实现,并为应用层提供接口。通过socket与内核中的网络协议栈进行交互。 socket是系统分配的,有四个重要信息,源IP,源port,目标IP,目标port,因此系统中的所有socket这4个信息,必须唯一,不能重复.
int m_pSocket;//socket就是一个整型变量
m_pSocket = socket(AF_INET, SOCK_STREAM, 0);//SOCK_STREAM:就是TCP,SOCK_DGRAM是UDP,
普及一下:TCP就是确保数据是正确的,完整的,UDP不保证,但其响应块,适合音视频等的传输,
你看到参数里没有socket的四个信息,这是下一步
已建立的socket与server的IP及port绑定关系.bind参数说明如下
int bind(
[in] SOCKET s, //就是前面建立的socket
const sockaddr *addr, //传入的地址结构体含IP,port
[in] int namelen //结构体的长度
);
代码中出现两个结构体sockaddr_in与sockaddr,有兴趣可参考附链接
struct sockaddr_in s_add;//sockaddr 是winsock定义网络地址的结构,包括IP,port
memset(&s_add, 0,sizeof(s_add));
s_add.sin_family = AF_INET;
s_add.sin_addr.s_addr = htonl(INADDR_ANY);//这里赋值为0
s_add.sin_port = htons(8900); //设置server的port
if (bind(m_pSocket, (struct sockaddr *)(&s_add), sizeof(struct sockaddr))<0)
{
printf("bind fail ! \r\n");
return NULL;
}
监听网络上这个端口是否有客户端的连接请求,listen()函数说明如下:
listen (
_In_ SOCKET s, //已绑定的socket
_In_ int backlog);//挂起连接队列的最大长度
backlog这个参数涉及到一些网络的细节。在进程正处理一个一个连接请求的时候,可能还存在其它的连接请求。因为TCP连接是一个过程,所以可能存在一种半连接的状态,有时由于同时尝试连接的用户过多,使得服务器进程无法快速地完成连接请求。如果这个情况出现了,服务器进程希望内核如何处理呢?内核会在自己的进程空间里维护一个队列以跟踪这些完成的连接但服务器进程还没有接手处理或正在进行的连接,这样的一个队列内核不可能让其任意大,所以必须有一个大小的上限。这个backlog告诉内核使用这个数值作为上限。
if(listen(m_pSocket, 1000)<0)//一般情况下是阻塞模式,除非修改socket的模式
{
printf("listen fail ! \r\n");
return NULL;
}
accept 函数提取socket上挂起连接队列中的第一个连接。 然后,它会创建并返回新套接字的句柄。这个句柄就确定了客户端的IP及port,这个socket就有完整的socket信息.
SOCKET WSAAPI accept(
[in] SOCKET s, //已绑定的socket
[out] sockaddr *addr, //连接成功会返回客户端地址
[in, out] int *addrlen //网址数据结构的长度
);
struct sockaddr client_sin; //建立空的客户端地址变量
int client_sin_len = sizeof(client_sin);
int c_pSocket = accept(m_pSocket, (struct sockaddr *)&client_sin, (int *)&client_sin_len )
//c_pSocket是连接成功返回一个新的socket
服务端是先接收数据再解析执行,因此这里重点介绍读数据.recv函数就是把该socket对应内核的数据复制出来,等待后面处理.
int recv(
[in] SOCKET s , //标识连接的socket,accept返回的socket。
[out] char *buf, //指向用于接收传入数据的缓冲区的指针。
[in] int len, //buf 参数指向的缓冲区的长度(以字节为单位)
[in] int flags //一组影响此函数行为的标志。一般是0
);
如果直接用这个函数就会导致一个问题,如果没有数据,就会导致recv阻塞,影响其他程序的执行,因此要在执行这个函数前加一个判断是否有数据,没有数据且超时就跳过.这就要介绍一个函数select(),利用 select 函数来判断某Socket上是否有数据可读,或者能否向一个套接字写入数据,
int WSAAPI select(
[in] int nfds, //忽略
[in, out] fd_set *readfds, //指向一组要检查的套接字是否可读。
[in, out] fd_set *writefds, //指向要检查的一组套接字是否可写。
[in, out] fd_set *exceptfds, //指向一组要检查错误的套接字的可选指针。
[in] const timeval *timeout //选择等待的最大时间
);
这里有两个数据结构:fd_set,timeval,
fd_set是一个放socket的集,或数组.结构如下:
typedef struct fd_set {
u_int fd_count; //集中的socket数
SOCKET fd_array[FD_SETSIZE]; //集中的套接字数组
} fd_set, FD_SET, *PFD_SET, *LPFD_SET;
Winsock 提供了下列宏操作,可用来针对 I/O活动,对 fdset 进行处理与检查:
● FD_CLR(s, *set):从set中删除套接字s。
● FD_ISSET(s, *set):检查s是否set集合的一名成员;如答案是肯定的是,则返回TRUE。
● FD_SET(s, *set):将套接字s加入集合set。
● FD_ZERO( * set):将set初始化成空集合。
timeval的数据结构如下:
typedef struct timeval {
long tv_sec; //秒
long tv_usec; //毫秒
} TIMEVAL;
fd_set readfds; //建立集
FD_ZERO(&readfds); //清空
FD_SET(m_pSocket, &readfds); //加socket到集
timeval tv = {1, 0 }; //设置超时为1.000秒
int iset = select(0, &readfds, NULL, NULL, &tv);
char * pReceiveBuf=new char[PacketLen];
if(iset > 0)
{
int iRet = recv(c_pSocket, pReceiveBuf,PacketLen,0);//返回收到字节数
if(iRet > 0)
{
onDataReceive(iBufPtr,iRet );//对数据解析,执行相应的动作
}
}
这个简单:closesock(c_pSocket);
这里仅介绍建立server的流程,因为文章的定位不同,如何让server同时响应多个客户端.及Client端的编程将来有时间再专题讲解.
sockaddr和sockaddr_in详解