CC++ SOCKET网络编程

CC++ SOCKET网络编程,第1张

首先,可以在客户端和服务器端协商一个传输协议,在这个传输协议中可以在数据包头增加一个字段,该字段描述后面数据的具体长度,这样在接收方就可以根据这个字段知道要分配多长的空间和什么时候结束。

然后,网络传输的字节序都是确定的,都是大端序(Big-Endian),因此不需要考虑大小端问题

存在两种字节顺序:NBO与HBO

网络字节顺序NBO(Network Byte Order):

按从高到低的顺序存储,在网络上使用统一的网络字节顺序,可以避免兼容性问题。

主机字节顺序(HBO,Host Byte Order):

不同的机器HBO不相同,与CPU设计有关

计算机数据存储有两种字节优先顺序:高位字节优先和低位字节优先。Internet上数据以高位字节优先顺序在网络上传输,所以对于在内部是以低位字节优先方式存储数据的机器,在Internet上传输数据时就需要进行转换。

  我们要讨论的第一个结构类型是:struct sockaddr,该类型是用来保存socket信息的:

  struct sockaddr {

  unsigned short sa_family; / 地址族, AF_xxx /

  char sa_data[14]; / 14 字节的协议地址 / };

  sa_family一般为AF_INET;sa_data则包含该socket的IP地址和端口号。

  另外还有一种结构类型:

  struct sockaddr_in {

   short int sin_family; / 地址族 /

   unsigned short int sin_port; / 端口号 /

   struct in_addr sin_addr; / IP地址 /

   unsigned char sin_zero[8]; / 填充0 以保持与struct sockaddr同样大小 /

  };

  这个结构使用更为方便。sin_zero(它用来将sockaddr_in结构填充到与struct sockaddr同样的长度)应该用bzero()或memset()函数将其置为零。指向sockaddr_in 的指针和指向sockaddr的指针可以相互转换,这意味着如果一个函数所需参数类型是sockaddr时,你可以在函数调用的时候将一个指向 sockaddr_in的指针转换为指向sockaddr的指针;或者相反。sin_family通常被赋AF_INET;sin_port和 sin_addr应该转换成为网络字节优先顺序;而sin_addr则不需要转换。

  我们下面讨论几个字节顺序转换函数:

  htons()--"Host to Network Short" ; htonl()--"Host to Network Long"

  ntohs()--"Network to Host Short" ; ntohl()--"Network to Host Long"

  在这里, h表示"host" ,n表示"network",s 表示"short",l表示 "long"。

  打开socket 描述符、建立绑定并建立连接

  socket函数原型为:

  int socket(int domain, int type, int protocol);

  domain参数指定socket的类型:SOCK_STREAM 或SOCK_DGRAM;protocol通常赋值“0”。Socket()调用返回一个整型socket描述符,你可以在后面的调用使用它。

  一旦通过socket调用返回一个socket描述符,你应该将该socket与你本机上的一个端口相关联(往往当你在设计服务器端程序时需要调用该函数。随后你就可以在该端口监听服务请求;而客户端一般无须调用该函数)。 Bind函数原型为:

  int bind(int sockfd,struct sockaddr my_addr, int addrlen);

  Sockfd是一个socket描述符,my_addr是一个指向包含有本机IP地址及端口号等信息的sockaddr类型的指针;addrlen常被设置为sizeof(struct sockaddr)。

  最后,对于bind 函数要说明的一点是,你可以用下面的赋值实现自动获得本机IP地址和随机获取一个没有被占用的端口号:

  my_addrsin_port = 0; / 系统随机选择一个未被使用的端口号 /

  my_addrsin_addrs_addr = INADDR_ANY; / 填入本机IP地址 /

  通过将my_addrsin_port置为0,函数会自动为你选择一个未占用的端口来使用。同样,通过将 my_addrsin_addrs_addr置为INADDR_ANY,系统会自动填入本机IP地址。Bind()函数在成功被调用时返回0;遇到错误时返回“-1”并将errno置为相应的错误号。另外要注意的是,当调用函数时,一般不要将端口号置为小于1024的值,因为1~1024是保留端口号,你可以使用大于1024中任何一个没有被占用的端口号。

  Connect()函数用来与远端服务器建立一个TCP连接,其函数原型为:

  int connect(int sockfd, struct sockaddr serv_addr, int addrlen);

  Sockfd是目的服务器的sockt描述符;serv_addr是包含目的机IP地址和端口号的指针。遇到错误时返回-1,并且errno中包含相应的错误码。进行客户端程序设计无须调用bind(),因为这种情况下只需知道目的机器的IP地址,而客户通过哪个端口与服务器建立连接并不需要关心,内核会自动选择一个未被占用的端口供客户端来使用。

  Listen()——监听是否有服务请求

  在服务器端程序中,当socket与某一端口捆绑以后,就需要监听该端口,以便对到达的服务请求加以处理。

  int listen(int sockfd, int backlog);

  Sockfd是Socket系统调用返回的socket 描述符;backlog指定在请求队列中允许的最大请求数,进入的连接请求将在队列中等待accept()它们(参考下文)。Backlog对队列中等待服务的请求的数目进行了限制,大多数系统缺省值为20。当listen遇到错误时返回-1,errno被置为相应的错误码。

  故服务器端程序通常按下列顺序进行函数调用:

  socket(); bind(); listen(); / accept() goes here /

  accept()——连接端口的服务请求。

  当某个客户端试图与服务器监听的端口连接时,该连接请求将排队等待服务器accept()它。通过调用accept()函数为其建立一个连接,accept()函数将返回一个新的socket描述符,来供这个新连接来使用。而服务器可以继续在以前的那个 socket上监听,同时可以在新的socket描述符上进行数据send()(发送)和recv()(接收) *** 作:

  int accept(int sockfd, void addr, int addrlen);

  sockfd是被监听的socket描述符,addr通常是一个指向sockaddr_in变量的指针,该变量用来存放提出连接请求服务的主机的信息(某台主机从某个端口发出该请求);addrten通常为一个指向值为sizeof(struct sockaddr_in)的整型指针变量。错误发生时返回一个-1并且设置相应的errno值。

  Send()和recv()——数据传输

  这两个函数是用于面向连接的socket上进行数据传输。

  Send()函数原型为:

  int send(int sockfd, const void msg, int len, int flags);

  Sockfd是你想用来传输数据的socket描述符,msg是一个指向要发送数据的指针。

  Len是以字节为单位的数据的长度。flags一般情况下置为0(关于该参数的用法可参照man手册)。

  char msg = "Beej was here!"; int len, bytes_sent;

  len = strlen(msg); bytes_sent = send(sockfd, msg,len,0);

  Send()函数返回实际上发送出的字节数,可能会少于你希望发送的数据。所以需要对send()的返回值进行测量。当send()返回值与len不匹配时,应该对这种情况进行处理。

  recv()函数原型为:

  int recv(int sockfd,void buf,int len,unsigned int flags);

  Sockfd是接受数据的socket描述符;buf 是存放接收数据的缓冲区;len是缓冲的长度。Flags也被置为0。Recv()返回实际上接收的字节数,或当出现错误时,返回-1并置相应的errno值。

  Sendto()和recvfrom()——利用数据报方式进行数据传输

  在无连接的数据报socket方式下,由于本地socket并没有与远端机器建立连接,所以在发送数据时应指明目的地址,sendto()函数原型为:

  int sendto(int sockfd, const void msg,int len,unsigned int flags,const struct sockaddr to, int tolen);

  该函数比send()函数多了两个参数,to表示目地机的IP地址和端口号信息,而tolen常常被赋值为sizeof (struct sockaddr)。Sendto 函数也返回实际发送的数据字节长度或在出现发送错误时返回-1。

  Recvfrom()函数原型为:

  int recvfrom(int sockfd,void buf,int len,unsigned int flags,struct sockaddr from,int fromlen);

  from是一个struct sockaddr类型的变量,该变量保存源机的IP地址及端口号。fromlen常置为sizeof (struct sockaddr)。当recvfrom()返回时,fromlen包含实际存入from中的数据字节数。Recvfrom()函数返回接收到的字节数或当出现错误时返回-1,并置相应的errno。

  应注意的一点是,当你对于数据报socket调用了connect()函数时,你也可以利用send()和recv()进行数据传输,但该socket仍然是数据报socket,并且利用传输层的UDP服务。但在发送或接收数据报时,内核会自动为之加上目地和源地址信息。

  Close()和shutdown()——结束数据传输

  当所有的数据 *** 作结束以后,你可以调用close()函数来释放该socket,从而停止在该socket上的任何数据 *** 作:close(sockfd);

  你也可以调用shutdown()函数来关闭该socket。该函数允许你只停止在某个方向上的数据传输,而一个方向上的数据传输继续进行。如你可以关闭某socket的写 *** 作而允许继续在该socket上接受数据,直至读入所有数据。

  int shutdown(int sockfd,int how);

  Sockfd的含义是显而易见的,而参数 how可以设为下列值:

  ·0-------不允许继续接收数据

  ·1-------不允许继续发送数据

  ·2-------不允许继续发送和接收数据,均为允许则调用close ()

  shutdown在 *** 作成功时返回0,在出现错误时返回-1(并置相应errno)。

  DNS——域名服务相关函数

  由于IP地址难以记忆和读写,所以为了读写记忆方便,人们常常用域名来表示主机,这就需要进行域名和IP地址的转换。函数gethostbyname()就是完成这种转换的,函数原型为:

  struct hostent gethostbyname(const char name);

  函数返回一种名为hosten的结构类型,它的定义如下:

  struct hostent {

   char h_name; / 主机的官方域名 /

   char h_aliases; / 一个以NULL结尾的主机别名数组 /

   int h_addrtype; / 返回的地址类型,在Internet环境下为AF-INET /

   int h_length; /地址的字节长度 /

   char h_addr_list; / 一个以0结尾的数组,包含该主机的所有地址/

  };

 #define h_addr h_addr_list[0] /在h-addr-list中的第一个地址/

2、将主机的unsigned long值转换为网络字节顺序(32位):为什么要这样做呢?因为不同的计算机使用不同的字节顺序存储数据。因此任何从Winsock函数对IP地址和端口号的引用和传给Winsock函数的IP地址和端口号均时按照网络顺序组织的。

&<60;&<60;&<60;&<60;&<60; u_long&<60; htonl(u_long hostlong);

&<60;&<60;&<60;&<60;&<60; 举例:htonl(0)=0

&<60;&<60;&<60;&<60;&<60; htonl(80)= 1342177280

3、将unsigned long数从网络字节顺序转换位主机字节顺序,是上面函数的逆函数。&<60;&<60;&<60;&<60;&<60;&<60; u_long&<60; ntohl(u_long netlong);

&<60;&<60;&<60;&<60;&<60; 举例:ntohl(0)=0

&<60;&<60;&<60;&<60;&<60; ntohl(1342177280)= 80

1342177280 = 80256256256

4、将主机的unsigned short值转换为网络字节顺序(16位):原因同2:&<60;&<60;&<60;&<60;&<60;&<60; u_short&<60; htons(u_short hostshort);

&<60;&<60;&<60;&<60;&<60; 举例:htonl(0)=0

&<60;&<60;&<60;&<60;&<60; htonl(80)= 20480

5、将unsigned short数从网络字节顺序转换位主机字节顺序,是上面函数的逆函数。&<60;&<60;&<60;&<60;&<60;&<60; u_short&<60; ntohs(u_short netshort);

&<60;&<60;&<60;&<60;&<60; 举例:ntohs(0)=0

&<60;&<60;&<60;&<60;&<60; ntohsl(20480)= 80

20480 = 8-256 (大小端地址转换)

不同的CPU有不同的字节序类型 这些字节序是指整数在内存中保存的顺序 这个叫做主机序

最常见的有两种

1. Little endian:将低序字节存储在起始地址

2. Big endian:将高序字节存储在起始地址

LE little-endian

最符合人的思维的字节序

地址低位存储值的低位

地址高位存储值的高位

怎么讲是最符合人的思维的字节序,是因为从人的第一观感来说

低位值小,就应该放在内存地址小的地方,也即内存地址低位

反之,高位值就应该放在内存地址大的地方,也即内存地址高位

BE big-endian

最直观的字节序

地址低位存储值的高位

地址高位存储值的低位

为什么说直观,不要考虑对应关系

只需要把内存地址从左到右按照由低到高的顺序写出

把值按照通常的高位到低位的顺序写出

两者对照,一个字节一个字节的填充进去

  不同机器内部对变量的字节存储顺序不同,有的采用大端模式(big-endian),有的采用小端模式(little-endian)。

  大端模式是指高字节数据存放在低地址处,低字节数据放在高地址处。

  小端模式是指低字节数据存放在低地址处,高字节数据放在高地址处。

  在网络上传输数据时,由于数据传输的两端可能对应不同的硬件平台,采用的存储字节顺序也可能不一致,因此 TCP/IP 协议规定了在网络上必须采用网络字节顺序(也就是大端模式) 。

  通过对大小端的存储原理分析可发现,对于 char 型数据,由于其只占一个字节,所以不存在这个问题,这也是一般情况下把数据缓冲区定义成 char 类型 的原因之一。对于 IP 地址、端口号等非 char 型数据,必须在数据发送到网络上之前将其转换成大端模式,在接收到数据之后再将其转换成符合接收端主机的存储模式。

  Linux 系统为大小端模式的转换提供了 4 个函数,输入 man byteorder 命令可得函数原型:

  

  <EM><STRONG><SPAN>#include <arpa/ineth>

  uint32_t htonl()(uint32_t hostlong);

  uint16_t htons(uint16_t hostshort);

  uint32_t ntohl(uint32_t netlong);

  uint16_t ntohs(uint16_t netshort);</SPAN>

  </STRONG>

  </EM>

  htonl 表示 host to network long ,用于将主机 unsigned int 型数据转换成网络字节顺序;

  htons 表示 host to network short ,用于将主机 unsigned short 型数据转换成网络字节顺序;

  ntohl、ntohs 的功能分别与 htonl、htons 相反。

  通常使用的有两种数据类型:短型(两个字节)和长型(四个字节)。

  下面介绍的这些转换函数对于这两类的无符号整型变量都可以正确的转换。

  如果你想将一个短型数据从主机字节顺序转换到网络字节顺序的话,有这样一个函数htnos:

  它是以"h”开头的,代表“主机”;

  紧跟着它的是"to",代表“转换到”;

  然后是"n",代表“网络”;

  最后是"s",代表“短型数据”。

  你可以使用"n", "h", "to", "s", "l"的任意组合。当然,你要在可能的情况下进行组合。比如,系统是没有stolh()函数的(Short to Long Host ?)。

  下面给出套接字字节转换程序的列表:

  hotns()——"Host to NetWork Short",主机字节顺序转换为网络字节顺序(对无符号短型进行 *** 作 4bytes)

  htonl()——"Host to NetWork Long",主机字节顺序转换为网络字节顺序(对无符号长型进行 *** 作 8bytes)

  ntons()——"NetWork to Host short",网络字节序转换为主机字节顺序(对无符号短型进行 *** 作 4bytes)

  ntohl()——"NetWork to Host Long",网络字节顺序转换为主机字节顺序(对无符号长型进行 *** 作 8bytes)

  例如:sin_addrs_addr = htonl(innaddr_any)是什么意思?

  sin_addrs_addr = htonl(innaddr_any)是Socket编程中用到的。

  是任意定义的一个sockaddr_in型的结构体对象sin_addr是他的一个属性,用于定义IP地址,是strcut in_addr型的,s_addr为结构体in_addr的对象,简单说就是三个结构体嵌套包装的一个包。

  inaddr_any一般为内核指定的,大多数系统取0,表示任意的IP地址。

  htonl()简单说是把一个本机IP转化为网络协议中规定的格式的函数,也就是所谓的大端模式或小端模式。

  htons函数是将一个u_short类型的值从主机字节顺序转换为TCP/IP的网络字节顺序,原型声明如下:

  u_short htons(u_short hostshort);

  htonl函数是将一个u_long的值从主机字节顺序转换为TCP/IP的网络字节顺序,原型声明如下:

  u_long htonl(u_long hostlong);

  字节序和网络平台有关,不同的平台,字节序不同。(字节序顾名思义——字节的排列顺序)只有多于一个字节的数据类型,才有字节序的问题,比如short或者int类型。char是没有这个问题的。字节序就是在硬件里面,一般实在内存里,如何表示存储和表示这些数据类型。如果高字节放到高地址上,就是大端(big endian),如果高字节放到低地址上,就是小端模式(little endian)。

网络通讯,并且有些电脑也是大端序。

大端模式即高位字节存放在低地址中,低位字节存放在高地址中;小端模式相反,高位字节存放在高地址中,低位字节存放在低地址中。由于芯片都从低地址位开始取数据,那么如果代码输出0x01,表示高位字节0x01存放在低地址位,则此芯片采用大端模式;如果代码输出0x04,表示低位字节0x04存放在低地址位,则此芯片采用小端模式。大小端模式是一种存储方式,是针对芯片来说的,每一个芯片在出厂时都已经规定好了大小端,用户不可以更改。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/12176347.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-21
下一篇 2023-05-21

发表评论

登录后才能评论

评论列表(0条)

保存