[1] tcp通信原理
1. 建立连接
2. 通信
3. 断开连接
[2] TCP实现流程
1. 头文件
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
2. 数据结构
3. 实现流程(函数)
(1) 服务端
1. 创建监听socket 买手机
/*
* @param[in] domain 通信领域
* @li AF_UNIX, AF_LOCAL 本机通信
* @li AF_INET ipv4
* @li AF_INET6 ipv6
* @li AF_PACKET 底层数据包
* @param[in] type 套接字类型
* SOCK_STREAM 流式套接字(TCP)
* SOCK_DGRAM 报文套接字(UDP)
* SOCK_RAW 原始套接字(IP、ICMP)
* @param[in] protocol 指定使用协议,通常是0
* @return 文件描述符(套接字)
* @li -1 错误(错误码见errno)
*/
int socket(int domain, int type, int protocol);
2. 绑定ip地址(本机)和端口(自己的端口)到监听socket 放SIM卡
/*
* @brief 绑定ip地址(本机的)和端口(自己的)到socket
* @param[in] sockfd 套接字
* @param[in] addr 地址(通信类型、ip地址和端口号)
* @param[in] addrlen addr的字节数
* @return @li 0 绑定成功
* @li -1 错误(错误码见errno)
*/
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
3. listen(将socket设置为监听模式) 开机
/*
* @brief 将socket设置为监听模式
* @param[in] sockfd 套接字
* @param[in] backlog 设置listen队列的最大连接请求个数
* @return @li 0 成功
* @li -1 错误(错误码见errno)
*/
int listen(int sockfd, int backlog);
4. accept(接收客户端连接, 创建客户端socket) 接通
/*
* @brief 接收客户端连接, 创建跟客户端连接的socket
* @param[in] sockfd 监听套接字
* @param[out] addr 客户端地址(ip地址和端口)
* @param[in | out] addrlen 输入地址缓存的字节数,输出实际地址的字节数
* @return 文件描述符(客户端套接字)
* @li -1 错误(错误码见errno)
* @notes listen请求队列为空时,在阻塞模式下,本函数会阻塞
*/
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
5. 利用客户端socket接收/发送数据 说话
/*
* @brief 发送数据(写数据到发送缓冲区)
* @param[in] sockfd 客户端套接字
* @param[in] buf 要发送的数据
* @param[in] len 数据长度
* @param[in] flags 一般为0
* @return 成功发送的字节数
* @li -1 错误(错误码见errno)
* @notes 一个字节都无法发送时,在阻塞模式下,本函数会阻塞
* send(sockfd, buf, len, 0) <===> write(sockfd, buf, len);
*/
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
/*
* @brief 接收数据(读取数据从接收中缓冲区)
* @param[in] sockfd 客户端套接字
* @param[out] buf 放置数据的缓冲
* @param[in] len 缓冲区的字节数
* @param[in] flags 一般为0
* @return 成功读取的字节数
* @li -1 错误(错误码见errno)
* @notes 一个字节都无法接收时,在阻塞模式下,本函数会阻塞
* recv(sockfd, buf, sizeof(buf), 0) <===> read(sockfd, buf, sizeof(buf));
*/
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
6. close客户端socket 挂机
7. close监听socket 关机
int close(int fd);
/*
* @brief 关闭套接字
* @param[in] sockfd 套接字
* @param[in] how 如何关闭?
* @li SHUT_RD 关闭读
* @li SHUT_WR 关闭写
* @li SHUT_RDWR 关闭读写
* @return @li 0 成功
* @li -1 错误(错误码见errno)
*/
int shutdown(int sockfd, int how);
(2) 客户端
1. 创建socket
2. 绑定ip地址(本机)和端口(自己的端口)到socket(可选)
3. 建立连接(connect)
/*
* @brief 连接服务端
* @param[in] sockfd 套接字
* @param[in] addr 服务端地址(ip和端口)
* @param[in] addrlen 地址字节数
* @return @li 0 成功
* @li -1 错误(错误码见errno)
* @notes 正在连接时,在阻塞模式下,本函数会阻塞,但阻塞有超时时间
*/
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
4. 发送/接收数据
5. 关闭socket
[3] udp聊天室
1. 简单的多人聊天
见《4.udp聊天室》
2. 标准聊天
(1) 登陆(功能 名字)
(2) 聊天(功能 内容)
(3) 退出(功能)
1. 制定协议
打包(数据组合) 1. 结构体 2. 按顺序放数据到缓存
1. 结构体分析
typedef struct {
// char c;
// 中间会空3个字节
int func;
char c;
char *p;
} PROL ;
客户端 服务器 错误原因 解决
字节序 小端 大端 func会错误,func有四个字节,字节顺序正好相反 统一使用大端(htonl/ntohl htons/ntohs)
位数 32bit 64bit 机器理解int类型的时候,位数不同 系统没有提供统一方法,程序自己保证位数相同
对齐 32bit 64bit 因为结构体程序对齐,很可能导致中间,空闲的字节 认真设计结构体,是它尽量不出现因为需要对齐,而空出内存
数,不相同而导致解析错误
对于初学者,如果需要考虑数据传输完全正确,不要使用结构体。
2. 按顺序放数据到缓存
最简单的方法,保证协议中的数据都是以字节为单位
300 "300"
2.7 "2.7"