我对C语言中的socket编程有些困惑。
你创建一个socket,将其绑定到接口和IP地址并让其监听。我在一些网站上找到了一些相关资源,理解得很好。特别是,我发现一篇文章《Unix系统下的网络编程》非常有用。
让我困惑的是套接字中数据到达的时间。
如何确定数据包何时到达以及数据包的大小?需要自己完成所有繁重的工作吗?
我的基本假设是,数据包的长度可以变化,因此一旦二进制数据开始出现在socket中,你如何开始从中构建数据包呢?
我对C语言中的socket编程有些困惑。
你创建一个socket,将其绑定到接口和IP地址并让其监听。我在一些网站上找到了一些相关资源,理解得很好。特别是,我发现一篇文章《Unix系统下的网络编程》非常有用。
让我困惑的是套接字中数据到达的时间。
如何确定数据包何时到达以及数据包的大小?需要自己完成所有繁重的工作吗?
我的基本假设是,数据包的长度可以变化,因此一旦二进制数据开始出现在socket中,你如何开始从中构建数据包呢?
简短回答是你需要自己承担所有的重活。你可以被通知数据可读,但你不会知道有多少字节数可用。在大部分使用可变长度包的IP协议中,将会在包前面添加一个已知固定长度的头部。这个头部将包含包的长度。你先读取头部,获得包的长度,然后再读取整个包。你会一直重复这个模式(先读头部,再读包)直到通信完成。
当从套接字读取数据时,你请求特定数量的字节。读取调用可能会阻塞直至请求的字节数已被读取,但它可能会返回少于所请求的字节数。如果发生这种情况,你只需重试读取并请求剩余的字节即可。
下面是一个典型的 C 函数用于从套接字中读取指定数量的字节:
/* buffer points to memory block that is bigger than the number of bytes to be read */
/* socket is open socket that is connected to a sender */
/* bytesToRead is the number of bytes expected from the sender */
/* bytesRead is a pointer to a integer variable that will hold the number of bytes */
/* actually received from the sender. */
/* The function returns either the number of bytes read, */
/* 0 if the socket was closed by the sender, and */
/* -1 if an error occurred while reading from the socket */
int readBytes(int socket, char *buffer, int bytesToRead, int *bytesRead)
{
*bytesRead = 0;
while(*bytesRead < bytesToRead)
{
int ret = read(socket, buffer + *bytesRead, bytesToRead - *bytesRead);
if(ret <= 0)
{
/* either connection was closed or an error occurred */
return ret;
}
else
{
*bytesRead += ret;
}
}
return *bytesRead;
}
因此,你的问题的答案很大程度上取决于你使用UDP还是TCP作为传输协议。
对于UDP,情况变得简单了很多,你可以调用recv/recvfrom/recvmsg并指定你需要的数据包大小(你可能会从源头发送固定长度的数据包),并假设如果有可用数据,则它以数据包长度的倍数存在。(也就是说,你使用发送端数据包的大小调用recv*,然后就完成了)
对于TCP,情况变得更加有趣——为了说明这一点,我将假设你已经知道如何使用socket()、bind()、listen()和accept(),后者是获取新建连接的文件描述符(FD)的方法。
有两种方式可以对套接字进行I/O操作:阻塞和非阻塞。在阻塞模式下,你调用read(fd, buf, N),并且read函数等待直到你将N个字节读入buf中。而在非阻塞模式下,你必须使用select()或poll()检查FD是否可读,并且只有在FD可读时才能进行read()操作。
当处理基于TCP的连接时,操作系统不关心数据包的大小,因为它被视为一个连续的数据流,而不是独立的数据包大小块。
如果你的应用程序使用"packets"(打包或解包的数据结构),你应该能够使用适当的大小参数调用read(),并一次从套接字中读取整个数据结构。唯一需要注意的限制是要记得正确地处理发送数据的字节顺序,以防源系统和目标系统具有不同的字节顺序。这适用于UDP和TCP。
就*NIX套接字编程而言,我强烈推荐W. Richard Stevens的《Unix网络编程,第1卷》(UNPv1)和《Unix环境高级编程》(APUE)。前者是一本关于基于网络的编程的巨著,无论传输方式如何,后者是一本涵盖*NIX编程的好书。此外,还可以查找《TCP/IP Illustrated》第1卷和第2卷。