《C编程.高级C》3.网络

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_STREAMSOCK_DGRAMsocket()相同的方式使用结果。重要的是要记住,UDP不保证数据包的传送和传送顺序,因此检查很重要。

如果你想利用UDP的属性,那么你可以使用sendto()recvfrom(),这就像操作send()recv()除了你需要提供额外的参数指定你与谁沟通。

我该如何检查错误?

功能socket()recv()并且connect()所有返回-1失败和使用错误号进一步的细节。

猜你想读:《C编程.高级C》4.序列化

THE END
分享