头文件

<arpa/inet.h>

大小端转换

网络大小端转换:htonshtons表示从本地的小端模式(主机字节序)转到服务器的大端模式(网络字节序)。分别是对应16位的short和32位的long。

如果是从网络字节序转到主机字节序,则是 ntohsntohl

IP地址的大小端转换

  • 将字符串的IP地址进行大小端转换:
    • 本机到服务端:inet_pton(int 地址族 , const char* 要转换的点分十进制IP地址, void * 转换成功后大端整数IP的写入地址)
      • 地址族:AF_INET为ipv4、AF_INET6为ipv6
      • 返回值:1表示成功,失败为0或者-1
    • 大端到小端:inet_ntop(int 地址族, const void* 大端IP整数地址, char *得到的小端地址, socklen_t 第三个参数对应的内存字节数)
      • 参数同上
      • 返回值:成功就返回第三个参数的地址,通过返回值取出转哈un的IP,失败NULL
    • 只能处理ipv4的一组函数:
      • 小转大:int_addr_t inet_addr(const char *cp)
      • 大转小:char* inet_ntoa(struct in_addr in);

可以看出,小端的是字符串,大端的是整数型。

socket数据结构

//第一种,sockaddr
struct sockaddr{
    sa_family_t sa_family; //地址族协议
    char sa_data[14]; //2字节端口,4字节IP,8字节不管
};

//第二种,sockaddr_in
//两种字节数相同,推荐用后者。
struct sockaddr_in{
    sa_family_t sin_family; // 地址族协议,AF_INET
    in_port_t sin_port; //端口,2字节,大端
    struct in_addr sin_addr; // IP地址,4字节,大端
    //填充的八个字节,这里不写出来了。
}

socket函数

创建套接字

  • int socket(int 地址族协议, int 传输协议, int 协议)
    • 第一个参数:AF_INE或者AF_INET6
    • 第二个参数:
      • SOCK_STREAM 流式传输
      • SOCK_DGRAM 报文传输
    • 第三个参数:一般写0,默认如果流式使用TCP,报式使用UDP
    • 返回值:
      • 成功:返回套接字文件描述符,指向内核中的某一块内存,网络通信基于这个文件描述符。
      • 失败:-1

绑定IP和端口

  • int bind(int socket文件描述符fd, const struct sockaddr *addr, socklen_t addlen)
    • 第一个参数:上面函数返回的文件描述符。
    • 第二个参数:IP和端口封装的结构体,一般用sockaddr_in 强转为sockaddr 代替。
    • 第三个参数:sizeof addr
    • 返回值
      • 成功0,失败-1

监听

  • int listen(int socket文件描述符fd, int backlog)
    • 第二个参数为最大连接要求,最大值是128.
    • 返回值:成功0,失败-1

阻塞并接收客户端的连接请求,建立新连接,得到通信的文件描述符

  • int accept(int 监听的文件描述符, struct sockaddr* addr , socklen_t * addrlen)
    • 第一个参数:监听的文件描述符
    • 第二个参数,存储建立连接的服务端的地址信息
    • 第三个参数:存储addr指向的内存大小。
    • 返回值:成功返回一个文件描述符,用于和这个客户端建立通信,调用失败返回-1
  • 这个函数是一个阻塞函数,没有新的客户端请求的时候会阻塞。

接收数据

  • ssize_t read(int 通信文件描述符, void *buf 用于存储接受数据的有效内存, size_t size 内存容量)
  • ssize_t recv(int 通信文件描述符, void *buf 用于存储接受数据的有效内存, size_t size 内存容量, int flags)
    • 第一个是accept 返回的文件描述符
    • 第四个参数,一般不使用,指定为0
  • 返回值
    • >0 为实际接受的字节数
    • =0 对方断开连接
    • -1 接收数据失败
  • 如果连接没有断开,会阻塞直到数据到达。当发送方断开连接不会阻塞,直接返回0.

发送数据

  • ssize_t write(int 通信文件描述符, const void* buf 要发送的字符串, size_t len )
  • ssize_t send(int 通信文件描述符, const void* buf 要发送的字符串, size_t len,int flags )
  • 返回值
    • >0 为实际发送的字节
    • -1 表示数据发送失败

connect

  • 成功连接服务器后,客户端这边会随机绑定一个IP和端口。
  • 服务器调用的accept 就是存储客户端的IP和端口信息
  • int connect(int 通信文件描述符, const struct sockaddr *addr, socklen_t addrlen)
    • 第一个参数,通信的文件描述符。
    • 第二个参数,连接的服务端的地址信息,需要先转换为大端
    • 第三个参数,addr指向的内存的大小。
    • 返回值
      • 成功0,失败-1

TCP通信流程

服务端

  1. 创建监听套接字,即调用socket函数。
  2. 将得到的文件描述符和本地端口绑定。
  3. 开始监听
  4. 等待客户端发送请求,连接成功后得到一个通信的文件描述符,没有新连接请求就阻塞。
  5. 通信,读写操作也是阻塞的。
  6. 关闭套接字,有两个。(监听的文字描述符只需要一个,负责监测客户端的请求,不负责通信;通信的文件描述符,如果有N个客户端进行通信就有N个)

服务端实现代码

服务端代码

客户端实现代码

客户端代码