编写socket通信必备知识

1. sys/types.h    数据类型定义

2. sys/socket.h  提供socket函数及数据结构

3. netinet/in.h    定义数据结构 sockaddr_in

4. arpa/inet.h     提供IP地址转换函数

5. netdb.h          提供设置及获取域名的函数

6. sys/ioctl.h      提供对I/O控制的函数

7. sys/poll.h       提供socket等待测试机制的函数

其他在网络程序中常见的头文件

1. unistd.h         提供通用的文件,目录,程序及进程操作的函数

2. errno.h          提供错误号errno的定义,用于错误处理

3. fcntl.h            提供对文件控制的函数

4. time.h            提供有关时间的函数

5. crypt.h           提供使用DES加密算法的加密函数

6. pwd.h            提供对/etc/passwd文件访问的函数

7. pthread.h      提供多线程操作的函数

8. signal.h         提供对信号操作的函数

9. sys/wait.h、sys/ipc.h、sys/shm.h   提供进程等待、进程间通讯(IPC)及共享内存函数


struct sockadd{

         unsigned short sa_family;

         char sa_data[14];

}

此数据结构用做bind、connect、recvfrom、sendto等函数的参数,指明地址信息。但一般编程中并不直接针对此数据结构进行操作,而是使用另一个与sockaddr等价的数据结构sockaddr_in。

//sa_family成员是地址族类型(sa_family_t)的变量,地址族类型通常与协议族类型对应。常见的协议族(protocol family,也称domain)

//============================================

// 协议族       |        地址族         |        描述       

//       PF_UNIX    |      AF_UNIX       |   UNIX本地域协议族

//       PF_INET    |      AF_INET        |   TCP/IPv4协议族

//       PF_INET6  |      AF_INET6      |   TCP/IPv6协议族

//============================================

宏PF_* 和AF_* 都定义在bits/socket.h头文件中,且后者与前者有完全相同的值,所以二者通常混用。

//sa_data成员用于存放socket地址值。但是不同协议族的地址值具有不同的含义和长度。

//====================================================================

//    协议族       |       地址值含义和长度

//    PF_UNIX   |     文件的路径名,长度可达到108字节

//    PF_INET    |      16bit端口号和32bit IPv4地址,共6字节

//    PF_INET6  |     16bit端口号,32bit流标识,128bit IPv6地址,32bit范围ID,共26字节

//====================================================================

struct sockaddr_in{

      short int sin_family;      //指代协议族,在socket编程中通常用AF_INET

      unsigned short int sin_port;    //存储端口号(使用网络字节顺序,网络字节顺序,采用统一的big endian排序,即数据的高字节,保持在内存的低地址中。)

      struct in_addr sin_addr;         //存储IP地址,使用in_addr这个数据结构。

      unsigned char sin_zero[8];    //是为了让sockaddr和sockaddr_in两个数据结构保持大小相同而保留的空字节。

}  

typedef struct in_addr{

      unit{

            struct{

                  unsigned char s_b1,s_b2,s_b3,s_b4;

            }S_un_b;

            struct{

                  unsigned short s_w1, s_w2;

            }S_un_w;

            unsigned long s_addr;

      }S_un;

}IN_ADDR;

in_addr 的含义就是用共同体存储ip的三种表达:

1. 用四个字节表示IP地址的四个数字;

2. 用两个双字节来表示IP地址;

3. 用一种长整形来表示IP地址;

给 in_addr赋值的最简单方法是使用inet_addr函数,sockaddr_in.sin_addr.s_addr=inet_addr("192.168.1.1");

//常用函数如下:

1. 转换函数

      unsigned long net_addr(const char *cp);

      char * inet_ntoa(struct int_addr in);

2. 字节序转换函数

      htons() 主机字节序转网络字节序 short

      htonl()  主机字节序转网络字节序 long

      ntonl()  网络字节序 short 转主机字节序

      ntonl()  网络字节序 long 转主机字节序

3. 取得本地主机名

      int gethostname(char *hostname, size_t size);

4. 取得本地信息

      int getsockname(int sockfd, struct sockaddr* addr, int *addrlen);

5. 取得socket的对方地址

      int getpeername(int s,struct sockaddr *name,socklen_t  *namelen);

6. 获取DNS信息

      struct hostent *gethostbyname(const char* name);

      struct hostent *gethostbyaddr(const char* addr, int len,int type);

7. 读取或改变socket属性

      int getsockopt(int sockfd,int level, int name, char* value,int * optlen);

      int setsockopt(int sockfd,int level,int optname, const void* optval,socklen_t optlen);

8. 轮询

      int select(int numfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval* timeout);

      // 参数一: 最大文件描述符+1(本参数忽略,仅起到兼容作用);

      // 参数二: 指向一组等待可读性检查的套接口;(是否有数据读)

      // 参数三: 指向一组等待可写性检查的套接口;

      // 参数四: 指向一组等待错误检查的套接口;

      // 参数五: 轮询等待的最多时间,对于阻塞操作则为NULL;

      // FD_CLR(int fd,fd_set *set);用来清除描述set中相关fd的位

      // FD_ISSET(int fd,fd_set* set);用来测试描述词组set中相关fd的位是否为真

      // FD_SET(int fd, fd_set *set);  用来设置描述词组set中的相关fd的位

      // FD_ZERO(fd_set *set);用来清除描述词组set的全部位

通常情况下服务器端socket的步骤如下:

1. 建立socket

      TCP: sockfd = socket(AF_INET,SOCK_STREAM, 0);

      UDP:   sockfd = socket(AF_INET,SOCK_DGRAM, 0);

2. 绑定端口

      int bind(int sockfd,struct sockaddr *sa, int addrlen);

3. 监听端口

      int listen(int sockfd,int queue_length);

4. 响应请求

      int accept(int socked,struct sockaddr* addr, int *addrlen);

5. 关闭连接

      int close(int sockfd);当进程结束时,系统内核会调用close(fd)关闭文件描述符,最好是手动调用函数关闭,否则长期以往会占用大量的文件描述符和系统资源(比如  说:长期的网络服务器)

      int shutdown(int sockfd,int how) 是专门针对socket的操作函数,how可以取0--禁止接收、1--禁止发送、2--禁止收发。

      //==============================================================================================================

      //     可选值              |          含义

      //     SHUT_RD        |  关闭sockfd上读的这一半,应用程序不能再针对socket文件描述符进行读操作,并且该socket接收缓冲区中的数据都被丢弃

      //     SHUT_WR       |  关闭sockfd上写的这一半,socket的发送缓冲区中的数据会再真正关闭连接之前全部发送出去,应用程序不可再对该socket

      //                              |  文件描述符执行写操作,这中国情况下,连接处于半关闭状态

      //     SHUT_RDWR  |  同时关闭sockfd上的读和写

      //==============================================================================================================

通常情况下客户端socket的步骤如下:

1. 建立socket

      TCP: sockfd = socket(AF_INET,SOCK_STREAM,0);

      UDP:   sockfd = socket(AF_INET,SOCK_DGRAM,0);

2. 连接服务器
      connect(sockfd , (struct sockaddr*)address, sizeof(address));
      //address 是服务器的通讯的结构体,需要进行初始化,比如说:设置协议族,设置上服务器的IP地址及监听端口号等等如:
      //        struct sockaddr_in address;
      //        bzero(&address,sizeof(address));
      //        address.sin_family = AF_INET; //add
      //        inet_pton(AF_INET, ip, &address.sin_addr);
      //        address.sin_port = htons(port);
3. 关闭连接
     如同服务器关闭连接

在服务器与客户端直接的通信主要是TCP和UDP方式,socket编程接口提供了几个专门的数据读写函数分别如下:
在TCP通信模式下:
      1.  ssize_t recv(int sockfd, void *buf, size_t len, int flags);
           recv 读取sockfd上的数据,buf 和 len 参数分别指定读缓冲区的位置和大小,flag的参数为数据收发提供了额外的控制,详情如下:
//=======================================================================================================================//
//           选项名                                                                                           含义                                                                                                            send  \   recv  
//  MSG_CONFIRM          指示数据链路层协议持续监听对方的回应,直到得到答复。它仅能用于SOCK_DGRAM 和 SOCK_RAW类型的socket         Y   \    N    
//  MSG_DONTROUTE    不查看路由表,直接将数据发送给本地局域网络内的主机。这标识发送者确切地知道目标主机就在本地网络上                     Y   \    N    
//  MSG_DONTWAIT        对socket的此次操作将是非阻塞的                                                                                                                                            Y   \   Y    
//  MSG_MORE                告诉内核应用程序还有更多数据要发送,内核将超市等待新数据写入TCP发送缓冲区后一并发送。这样可以防止TCP发       Y   \   N   
//                                          送过多小的报文段,从而提供传输效率                                                                                                                                                  
//  MSG_WAITALL            该操作仅在读取到指定数量的字节后才返回                                                                                                                             N   \   Y   
//  MSG_PEEK                 窥探读缓存中的数据,此次读操作不会导致这些数据被清除                                                                                                     N   \   Y   
//  MSG_OOB                  发送给你或接收紧急数据                                                                                                                                                            Y   \   Y   
//  MSG_NOSIGNAL        往读端关闭的管道或者socket连接中写数据时不引发SIGPIPE信号                                                                                           Y   \   N   
//=======================================================================================================================//
       2. ssize_t send(int socked,const void *buf,size_t len,int flags);
           send往sockfd上写入数据,buf和len参数分别指定写缓冲区的位置和大小,send返回成功时返回实际写入的数据的长度,失败则返回-1并设置errno。 
在UDP通信模式下:
       1. ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr* src_addr, socklen_t* addrlen);
           recvfrom 读取sockfd上的数据,buf和len参数分别指定读取缓冲区的位置和大小。因为UDP通信没有连接的概念,所以我们每次读取数据都需要获取发送端的socket地                址,即参数src_addr所指的内容,addrlen参数则指定该地址的长度。
       2. ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr* dest_addr, socklen_t addrlen);
           sendto 往sockfd上写入数据,buf 和 len 参数分别指定写缓冲区的位置和大小。 dest_addr参数指定接收端的socket地址,addrlen参数则指定该地址的长度。
       这两个系统调用的flags参数以及返回值的含义均与send/recv系统调用的flags参数及返回值相同。
socket编程接口还提供了一对通用的数据读写接口,不仅能用于TCP数据流还能够用于UDP数据报:
       1. ssize_t recvmsg(int sockfd, struct msghdr* msg, int flags);
       2. ssize_t sendmsg(int sockfd, struct msghdr* msg, int flags);
       sockfd 参数指定被操作的目标socket。msg参数是msghdr结构体类型的指针,msghdr结构体定义如下:
       struct msghdr{
                 void * msg_name;                     // socket 地址
                 socklen_t  msg_namelen;         // socket 地址的长度
                 struct iovec* msg_iov;               // 分散的内存快
                 int msg_iovlen;                          // 分散内存块的数量
                 void * msg_control;                   // 指向辅助数据的起始位置
                 socklen_t msg_controllen;        // 辅助数据的大小
                 int  msg_flags;                           // 复制函数中的flags参数,并在过程调用中更新
       }      
       msg_name 成员指向一个socket地址结构变量。它指定通信对方的socket地址。对于面向连接的TCP协议,该成员没有意义,必须设置位NULL。这是因为对数据流socket而言,对方的地址已经知道。msg_namelen成员则指定了msg_name所指socket地址的长度。
       msg_iov成员是iovec结构体类型的指针,iovec结构体的定义如下:
       struct iovec{
                 void *iov_base;      // 内存起始地址
                 size_t  iov_len;    // 这块内存的长度
       }
       由上可见,iovec结构体封装了一块内存的起始位置和长度。msg_iovlen指定这样的iovec结构对象有多少个。对于recvmsg而言,数据将被读取并存放在msg_iovlen块分散的内存中,这些内存的位置和长度则由msg_iov指向的数据指定,这称为分散读;对于sendmsg而言,msg_iovlen块分散内存中的数据将一并发送,这称为集中写。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值