UNIX下的网络编程在C中比较简单。
本指南假设您已经对C,UNIX和网络有了一个很好的总体思路。
一个简单的客户端
首先,我们将介绍您可以做的最简单的事情之一:初始化流连接并从远程服务器接收消息。
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa / inet.h> #include <sys / types.h> #include <netinet / in.h> #include <sys / socket.h> #define MAXRCVLEN 500 #define PORTNUM 2300 int main (int argc , char * argv []) { char buffer [ MAXRCVLEN + 1 ]; / * +1所以我们可以添加null终止符* / int len , mysocket ; struct sockaddr_in dest ; mysocket = socket (AF_INET , SOCK_STREAM , 0 ); memset (&dest , 0 , sizeof (dest )); / *将struct * / dest 归零。sin_family = AF_INET ; DEST 。sin_addr 。s_addr = htonl (INADDR_LOOPBACK ); / *设置目标IP号码 - localhost,127.0.0.1 * / dest 。sin_port = htons (PORTNUM ); / *设置目标端口号* / connect (mysocket , (struct sockaddr * )&dest , sizeof (struct sockaddr_in )); LEN = 的recv (mysocket , 缓冲器, MAXRCVLEN , 0 ); / *我们必须自行终止接收的数据* / buffer [ len ] = '\ 0' ; printf (“Received%s(%d bytes)。\ n ” , buffer , len ); 关闭(mysocket ); 返回 EXIT_SUCCESS ; }
这是客户的骨干; 在实践中,我们会检查我们调用失败的每个函数,但是为了清楚起见,错误检查被遗漏了。
如您所见,代码主要围绕dest
哪个类型的结构sockaddr_in
。此结构存储有关我们要连接的计算机的信息。
mysocket = socket (AF_INET , SOCK_STREAM , 0 );
该socket()
函数告诉我们的OS我们想要一个套接字的文件描述符,我们可以用它来进行网络流连接; 这些参数意味着什么现在几乎无关紧要。
memset (&dest , 0 , sizeof (dest )); / *将struct * / dest 归零。sin_family = AF_INET ; DEST 。sin_addr 。s_addr = inet_addr (“127.0.0.1” ); / *设置目标IP号* / dest 。sin_port = htons (PORTNUM ); / *设置目标端口号* /
现在我们开始讨论有趣的部分:
第一行用于memset()
将结构归零。
第二行设置地址族。这应该与作为第一个参数传递的值相同socket()
; 对于大多数目的,AF_INET
将服务。
第三行是我们设置我们需要连接的机器的IP的地方。变量dest.sin_addr.s_addr
只是以Big Endian格式存储的整数,但我们不必知道,因为inet_addr()
函数将为我们执行从字符串到Big Endian整数的转换。
第四行设置目标端口号。该htons()
函数将端口号转换为Big Endian短整数。如果你的程序将仅在使用Big Endian数字作为默认值的机器上运行,那么它dest.sin_port = 21
也可以正常工作。但是,为了便携性,htons()
应始终使用原因。
现在所有的初步工作都完成了,我们实际上可以建立连接并使用它:
connect (mysocket , (struct sockaddr * )&dest , sizeof (struct sockaddr_in ));
这告诉我们的操作系统使用套接字mysocket
创建与指定的机器的连接dest
。
LEN = 的recv (mysocket , 缓冲器, MAXRCVLEN , 0 );
现在,它MAXRCVLEN
从连接接收最多数据字节并将它们存储在缓冲区字符串中。收到的字符数由recv()
。返回。重要的是要注意,当存储在缓冲区中时,收到的数据不会自动被终止,因此我们需要自己完成buffer[len] = '\0'
。
这就是它!
学习如何接收数据后的下一步是学习如何发送数据。如果你已经理解了上一节,那么这很容易。您所要做的就是使用send()
函数,该函数使用与之相同的参数recv()
。如果在我们之前的例子中buffer
有我们想要发送的文本并且它的长度存储在len
我们写的中send(mysocket, buffer, len, 0)
。send()
返回已发送的字节数。重要的是要记住send()
,由于各种原因,可能无法发送所有字节,因此检查其返回值是否等于您尝试发送的字节数非常重要。在大多数情况下,可以通过重新发送未发送的数据来解决此问题。
一个简单的服务器
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa / inet.h> #include <sys / types.h> #include <netinet / in.h> #include <sys / socket.h> #define PORTNUM 2300 int main (int argc , char * argv []) { char * msg = “Hello World!\ n ” ; struct sockaddr_in dest ; / *有关连接到我们的机器的套接字信息* / struct sockaddr_in serv ; / *关于我们服务器的套接字信息* / int mysocket ; / * socket用于侦听传入的连接* / socklen_t socksize = sizeof (struct sockaddr_in ); memset (&serv , 0 , sizeof (serv )); / *在填充字段* / serv 之前将结构置零。sin_family = AF_INET ; / *设置TCP / IP * / serv 的连接类型。sin_addr 。s_addr = htonl (INADDR_ANY ); / *将我们的地址设置为任何接口* / serv 。sin_port = htons (PORTNUM ); / *设置服务器端口号* / mysocket = socket (AF_INET , SOCK_STREAM , 0 ); / *将serv信息绑定到mysocket * / bind (mysocket , (struct sockaddr * )&serv , sizeof (struct sockaddr )); / *开始监听,允许最多1个挂起连接的队列* / listen (mysocket , 1 ); INT consocket = 接受(mysocket , (结构 的sockaddr * )&DEST , &socksize ); 而(consocket ) { printf的(“%s的传入连接-发送欢迎\ n ” , INET_NTOA (DEST 。sin_addr )); send (consocket , msg , strlen (msg ), 0 ); 关闭(consocket ); consocket = 接受(mysocket , (结构 的sockaddr * )&DEST , &socksize); } 关闭(mysocket ); 返回 EXIT_SUCCESS ; }
从表面上看,这与客户非常相似。第一个重要的区别是,我们不是创建一个sockaddr_in
有关我们连接的机器的信息,而是使用有关服务器的信息创建它,然后我们将bind()
其创建到套接字。这允许机器知道sockaddr_in
应该由我们指定的套接字处理的端口上接收的数据。
listen()
然后该函数告诉我们的程序使用给定的套接字开始监听。第二个参数listen()
允许我们指定可排队的最大连接数。每次与服务器建立连接时,都会将其添加到队列中。我们使用该accept()
函数从队列中获取连接。如果队列上没有等待连接,程序将等待直到收到连接。该accept()
函数返回另一个套接字。这个套接字本质上是一个“会话”套接字,可以单独用于与我们从队列中取出的连接进行通信。原始套接字(mysocket
)继续侦听指定的端口以进一步连接。
一旦我们有了“会话”套接字,我们就可以像处理客户端一样处理它,使用send()
和recv()
处理数据传输。
请注意,此服务器一次只能接受一个连接; 如果要同时处理多个客户端,则需要fork()
关闭单独的进程或使用线程来处理连接。
有用的网络功能
int gethostname (char * hostname , size_t size );
参数是指向字符数组的指针和该数组的大小。如果可能,它会找到主机名并将其存储在数组中。失败时返回-1。
struct hostent * gethostbyname (const char * name );
此函数获取有关域名的信息并将其存储在hostent
结构中。hostent
结构中最有用的部分是(char**) h_addr_list
字段,该字段是与该域关联的IP地址的空终止数组。该字段h_addr
是指向h_addr_list
数组中第一个IP地址的指针。NULL
失败时的回报。
常见问题
无状态连接怎么样?
如果您不想在程序中利用TCP的属性而宁愿只使用UDP连接,那么您只需在调用中替换SOCK_STREAM
并SOCK_DGRAM
以socket()
相同的方式使用结果。重要的是要记住,UDP不保证数据包的传送和传送顺序,因此检查很重要。
如果你想利用UDP的属性,那么你可以使用sendto()
和recvfrom()
,这就像操作send()
和recv()
除了你需要提供额外的参数指定你与谁沟通。
我该如何检查错误?
功能socket()
,recv()
并且connect()
所有返回-1失败和使用错误号进一步的细节。
猜你想读:《C编程.高级C》4.序列化