# 套接字通信
设计者开发的 接口
,以便应用程序调用该通信接口进行 通信
# 字节序
字节的顺机序,大于一个字节类型的数据在内存中的存放顺序。
Little-Endian
小端模式:数据的 低
位字节存在 低位
,PC 机默认低位Big-Endian
:大端模式 (网络字节序) 数据的 低位
字节存储到 高地
址位,套接字 通信
中操作的数据都是 大端存储
,包括: 接受/发送数据,IP地址,端口
# 字节转换函数
| #include <arpa/inet.h> |
| |
| uint16_t htons(uint16_t hostshort) |
| |
| uint32_t htonl(uint32_t hostlong) |
| |
| uint16_t ntohs(uint16_t netshort) |
| |
# IP
地址转换
| |
| int inet_pton(int af,const char *src,void *dst); |
| |
| |
| |
| |
| |
| |
| |
| #include <arpa/inet.h> |
| const char *inet_ntop(int af,const void *src,char *dst,socklen_t size); |
| |
| |
| |
# sockaddr
数据结构
| struct sockaddr { |
| sa_family_t sa_family; |
| char sa_data[14]; |
| } |
| |
| struct sockaddr_in |
| { |
| sa_family_t sin_family; |
| in_port_t sin_port; |
| struct in_addr sin_addr; |
| |
| unsigned char sin_zero[sizeof (struct sockaddr) - sizeof(sin_family) - |
| sizeof (in_port_t) - sizeof (struct in_addr)]; |
| }; |
# 套接字函数
# socket
套接字
| #include <arpa/inet.h> |
| |
| int socket(int domain,int type,int protocol); |
| |
| |
| |
| |
| |
| |
| |
# bind
绑定
| |
| int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); |
| |
| |
| |
| |
# listen
监听
| |
| int listen(int sockfd, int backlog); |
| |
| |
| |
# accept
接受
阻塞函数,在没有新的客户端连接请求的时候,该函数阻塞,返回一个文件描述符,基于此文件描述符和客户端通信
| |
| int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); |
| |
| |
| |
| |
# 接受数据
如果连接没有断开,接收端接受不到数据,接受数据的函数阻塞等待。数据到达后解除阻塞,开始接受数据,当发送端断开连接,接收端无法接收到任何数据,函数直接返回 0
| |
| ssize_t read(int sockfd, void *buf, size_t size); |
| ssize_t recv(int sockfd, void *buf, size_t size, int flags); |
| |
| |
| |
| |
| |
# 发送数据
| |
| ssize_t write(int fd, const void *buf, size_t len); |
| ssize_t send(int fd, const void *buf, size_t len, int flags); |
| |
| |
| |
# connect
| |
| |
| int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); |
| |
| |
# TCP Linux
通信
TCP 协议是一个安全的,面向连接的,流式传输协议。在客户端调用 connect 函数,完成三次握手,close 函数完成四次挥手。
# 三次握手
# 四次挥手
# socket
通信
# client.c
| #include <stdio.h> |
| #include <sys/types.h> |
| #include <sys/socket.h> |
| #include <arpa/inet.h> |
| #include <netinet/in.h> |
| #include <unistd.h> |
| #include <stdlib.h> |
| |
| #define PORT_ID 8800 |
| #define SIZE 100 |
| |
| |
| int main(int argc, char *argv[]) |
| { |
| int sockfd; |
| struct sockaddr_in server_addr; |
| char buf[SIZE]; |
| |
| if(argc < 2) |
| { |
| printf("Usage: ./client [server IP address]\n"); |
| exit(1); |
| } |
| |
| |
| sockfd = socket(AF_INET, SOCK_STREAM, 0); |
| |
| |
| server_addr.sin_family = AF_INET; |
| server_addr.sin_port = htons(PORT_ID); |
| |
| server_addr.sin_addr.s_addr = inet_addr(argv[1]); |
| |
| connect(sockfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)); |
| |
| |
| int re = recv(sockfd, buf, SIZE, 0); |
| if(re == 0) |
| { |
| printf("服务器断开了连接\n"); |
| } |
| else if(re < -1) |
| { |
| printf("接受数据失败\n"); |
| } |
| printf("Client receive from server: %s\n", buf); |
| sleep(1); |
| |
| |
| |
| close(sockfd); |
| |
| return 0; |
| } |
# server.c
| #include <stdio.h> |
| #include <sys/types.h> |
| #include <sys/socket.h> |
| #include <arpa/inet.h> |
| #include <netinet/in.h> |
| #include <unistd.h> |
| |
| |
| #define PORT_ID 8800 |
| |
| #define SIZE 100 |
| |
| int main(void) |
| { |
| int sockfd, client_sockfd; |
| struct sockaddr_in my_addr, client_addr; |
| int addr_len; |
| char welcome[SIZE] = "Welcome to connect to the sever!"; |
| |
| |
| sockfd = socket(AF_INET, SOCK_STREAM, 0); |
| if(sockfd == -1) |
| { |
| printf("套接字创建失败"); |
| } |
| |
| my_addr.sin_family = AF_INET; |
| my_addr.sin_port = htons(PORT_ID); |
| my_addr.sin_addr.s_addr = INADDR_ANY; |
| |
| int b = bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)); |
| if(b == -1) |
| { |
| printf("套接字与IP绑定失败"); |
| } |
| |
| int l = listen(sockfd, 10); |
| if(l == -1) |
| { |
| printf("套接字设置监听失败"); |
| } |
| addr_len = sizeof(struct sockaddr); |
| |
| while(1) |
| { |
| printf("Server is waiting for client to connect:\n"); |
| |
| client_sockfd = accept(sockfd, (struct sockaddr *)&client_addr, &addr_len); |
| |
| printf("Client IP address = %s\n", inet_ntoa(client_addr.sin_addr)); |
| |
| int sed = send(client_sockfd, welcome, SIZE, 0); |
| if(sed > 0) |
| { |
| printf("发送了%d字节的数据",sed); |
| } |
| else if(sed == 0) |
| { |
| printf("客户端断开了连接"); |
| } |
| else if(sed == -1) |
| { |
| printf("发送数据失败"); |
| } |
| printf("Disconnect the client request.\n"); |
| |
| close(client_sockfd); |
| } |
| |
| close(sockfd); |
| |
| return 0; |
| } |
# TCP流量控制
流量控制可以根据接收端的实际接受能力,接收端主机向发送端主机通知自己可以接受数据的大小,于是发送端发送不会超过该大小的数据,
TCP 首部,专门设置一个字段来通知窗口大小,当接收端的缓冲区面临数据溢出时,窗口的大小值也随之发生改变,设置一个更小的值通知发送端,从而控制数据的发送量,从而达到流量控制的目录 -- 滑动窗口
# TCP半关闭
TCP 连接只有一方发送了 FIN 请求连接,处于半关闭状态,数据仍可以单向通信。套接字本身时双向通信
- 服务器调用了 close 函数,不能发送数据,只能接受数据
- 客户端没有调用 close 函数,可以发送数据,但是不能接受数据
| #include <sys/socket.h> |
| |
| int shutdown(int sockfd,int how); |
| |
| |
| |
| |
| |
| |
# 端口复用
服务器进程主动断开连接,最后状态编程 TIME_WAIT 状态,这个进程会等待 2msl (1 分钟左右) 才会退出,如该进程不退出,其绑定的端口就不会释放。
| |
| int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen); |
| |
| |
| |
| |
| |
| |
| |
| int lfd = socket(AF_INET, SOCK_STREAM, 0); |
| if(lfd == -1) |
| { |
| perror("socket error"); |
| exit(1); |
| } |
| |
| |
| struct sockaddr_in serv_addr; |
| memset(&serv_addr, 0, sizeof(serv_addr)); |
| serv_addr.sin_family = AF_INET; |
| serv_addr.sin_port = htons(9999); |
| serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); |
| |
| |
| |
| |
| int opt = 1; |
| setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); |
| |
| |
| int ret = bind(lfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); |
# TCP 数据 粘包
处理
在发送数据块之前,在数据块最前边添加一个固定大小的数据头。数据头存储当前的总字节数,接收到先接受数据头,然后根据数据头结构对应字节大小的数据块
# 发送端实现
- 发送 N 数据长度申请
N+4
块大小内存 - 将待发送数据总长度写入前四个字节中 (需要转为大端
网络字节序
) - 待发送的数据拷贝到包头后边的地址空间,将完整数据包发送出去
- 释放申请的
堆空间
| |
| |
| |
| |
| int writen(int fd, const char* msg, int size) |
| { |
| |
| const char* buf = msg; |
| |
| int count = size; |
| while (count > 0) |
| { |
| |
| int len = send(fd, buf, count, 0); |
| if (len == -1) |
| { |
| close(fd); |
| return -1; |
| } |
| else if (len == 0) |
| { |
| continue; |
| } |
| |
| buf += len; |
| |
| count -= len; |
| } |
| return size; |
| } |
| |
| int sendMsg(int cfd, char* msg, int len) |
| { |
| if(msg == NULL || len <= 0 || cfd <=0) |
| { |
| |
| return -1; |
| } |
| |
| char* data = (char*)malloc(len+4); |
| int bigLen = htonl(len); |
| memcpy(data, &bigLen, 4); |
| memcpy(data+4, msg, len); |
| |
| int ret = writen(cfd, data, len+4); |
| |
| free(data); |
| return ret; |
| } |
# 接收端实现
- 将接受的
4字节
数据,从网络字节序转换为主机字节序 - 根据长度申请堆内存,用于存储
待接收的数据
- 根据得到的数据长度固定数目保存到堆内存中
- 处理
接受的数据
- 释放存储数据的
堆内存
| |
| int readn(int fd, char* buf, int size) |
| { |
| char* pt = buf; |
| int count = size; |
| |
| while (count > 0) |
| { |
| |
| int len = recv(fd, pt, count, 0); |
| if (len == -1) |
| { |
| return -1; |
| } |
| else if (len == 0) |
| { |
| return size - count; |
| } |
| pt += len; |
| count -= len; |
| } |
| return size; |
| } |
| |
| int recvMsg(int cfd, char** msg) |
| { |
| |
| |
| int len = 0; |
| |
| readn(cfd, (char*)&len, 4); |
| len = ntohl(len); |
| printf("数据块大小: %d\n", len); |
| |
| |
| char *buf = (char*)malloc(len+1); |
| |
| int ret = readn(cfd, buf, len); |
| if(ret != len) |
| { |
| close(cfd); |
| free(buf); |
| return -1; |
| } |
| buf[len] = '\0'; |
| *msg = buf; |
| return ret; |
| } |
# C++ 封装 TCP
# TcpSocket.h
| #include <string> |
| #include <unistd.h> |
| #include <sys/socket.h> |
| #include <string.h> |
| #include <arpa/inet.h> |
| #include <stdio.h> |
| #include <iostream> |
| using namespace std; |
| class TcpSocket |
| { |
| public: |
| TcpSocket(); |
| TcpSocket(int socket); |
| ~TcpSocket(); |
| int connectToHost(string ip, unsigned short port); |
| int sendMsg(string msg); |
| string recvMsg(); |
| |
| private: |
| int readn(char* buf, int size); |
| int writen(const char* msg, int size); |
| |
| private: |
| int m_fd; |
| }; |
# TcpSocket.cpp
| #include "TcpSocket.h" |
| |
| TcpSocket::TcpSocket() |
| { |
| m_fd = socket(AF_INET, SOCK_STREAM, 0); |
| } |
| |
| TcpSocket::TcpSocket(int socket) |
| { |
| m_fd = socket; |
| } |
| |
| TcpSocket::~TcpSocket() |
| { |
| if (m_fd > 0) |
| { |
| close(m_fd); |
| } |
| } |
| |
| int TcpSocket::connectToHost(string ip, unsigned short port) |
| { |
| |
| struct sockaddr_in saddr; |
| saddr.sin_family = AF_INET; |
| saddr.sin_port = htons(port); |
| inet_pton(AF_INET, ip.data(), &saddr.sin_addr.s_addr); |
| int ret = connect(m_fd, (struct sockaddr*)&saddr, sizeof(saddr)); |
| if (ret == -1) |
| { |
| perror("connect"); |
| return -1; |
| } |
| cout << "成功和服务器建立连接..." << endl; |
| return ret; |
| } |
| |
| int TcpSocket::sendMsg(string msg) |
| { |
| |
| char* data = new char[msg.size() + 4]; |
| int bigLen = htonl(msg.size()); |
| memcpy(data, &bigLen, 4); |
| memcpy(data + 4, msg.data(), msg.size()); |
| |
| int ret = writen(data, msg.size() + 4); |
| delete[]data; |
| return ret; |
| } |
| |
| string TcpSocket::recvMsg() |
| { |
| |
| |
| int len = 0; |
| readn((char*)&len, 4); |
| len = ntohl(len); |
| cout << "数据块大小: " << len << endl; |
| |
| |
| char* buf = new char[len + 1]; |
| int ret = readn(buf, len); |
| if (ret != len) |
| { |
| return string(); |
| } |
| buf[len] = '\0'; |
| string retStr(buf); |
| delete[]buf; |
| |
| return retStr; |
| } |
| |
| int TcpSocket::readn(char* buf, int size) |
| { |
| int nread = 0; |
| int left = size; |
| char* p = buf; |
| |
| while (left > 0) |
| { |
| if ((nread = read(m_fd, p, left)) > 0) |
| { |
| p += nread; |
| left -= nread; |
| } |
| else if (nread == -1) |
| { |
| return -1; |
| } |
| } |
| return size; |
| } |
| |
| int TcpSocket::writen(const char* msg, int size) |
| { |
| int left = size; |
| int nwrite = 0; |
| const char* p = msg; |
| |
| while (left > 0) |
| { |
| if ((nwrite = write(m_fd, msg, left)) > 0) |
| { |
| p += nwrite; |
| left -= nwrite; |
| } |
| else if (nwrite == -1) |
| { |
| return -1; |
| } |
| } |
| return size; |
| } |
# TcpServer.h
| #include "TcpSocket.h" |
| |
| class TcpServer |
| { |
| public: |
| TcpServer(); |
| ~TcpServer(); |
| int setListen(unsigned short port); |
| TcpSocket* acceptConn(struct sockaddr_in* addr = nullptr); |
| |
| private: |
| int m_fd; |
| }; |
# TcpServer.cpp
| #include "TcpServer.h" |
| |
| TcpServer::TcpServer() |
| { |
| m_fd = socket(AF_INET, SOCK_STREAM, 0); |
| } |
| |
| TcpServer::~TcpServer() |
| { |
| close(m_fd); |
| } |
| |
| int TcpServer::setListen(unsigned short port) |
| { |
| struct sockaddr_in saddr; |
| saddr.sin_family = AF_INET; |
| saddr.sin_port = htons(port); |
| saddr.sin_addr.s_addr = INADDR_ANY; |
| int ret = bind(m_fd, (struct sockaddr*)&saddr, sizeof(saddr)); |
| if (ret == -1) |
| { |
| perror("bind"); |
| return -1; |
| } |
| cout << "套接字绑定成功, ip: " |
| << inet_ntoa(saddr.sin_addr) |
| << ", port: " << port << endl; |
| |
| ret = listen(m_fd, 128); |
| if (ret == -1) |
| { |
| perror("listen"); |
| return -1; |
| } |
| cout << "设置监听成功..." << endl; |
| |
| return ret; |
| } |
| |
| TcpSocket* TcpServer::acceptConn(sockaddr_in* addr) |
| { |
| if (addr == NULL) |
| { |
| return nullptr; |
| } |
| |
| socklen_t addrlen = sizeof(struct sockaddr_in); |
| int cfd = accept(m_fd, (struct sockaddr*)addr, &addrlen); |
| if (cfd == -1) |
| { |
| perror("accept"); |
| return nullptr; |
| } |
| printf("成功和客户端建立连接...\n"); |
| return new TcpSocket(cfd); |
| } |
# server.cpp
| #include <pthread.h> |
| #include "TcpServer.h" |
| |
| struct SockInfo |
| { |
| TcpServer* s; |
| TcpSocket* tcp; |
| struct sockaddr_in addr; |
| }; |
| |
| void* working(void* arg) |
| { |
| struct SockInfo* pinfo = static_cast<struct SockInfo*>(arg); |
| |
| char ip[32]; |
| printf("客户端的IP: %s, 端口: %d\n", |
| inet_ntop(AF_INET, &pinfo->addr.sin_addr.s_addr, ip, sizeof(ip)), |
| ntohs(pinfo->addr.sin_port)); |
| |
| |
| while (1) |
| { |
| printf("接收数据: .....\n"); |
| string msg = pinfo->tcp->recvMsg(); |
| if (!msg.empty()) |
| { |
| cout << msg << endl << endl << endl; |
| } |
| else |
| { |
| break; |
| } |
| } |
| delete pinfo->tcp; |
| delete pinfo; |
| return nullptr; |
| } |
| |
| int main() |
| { |
| |
| TcpServer s; |
| |
| s.setListen(10000); |
| |
| while (1) |
| { |
| SockInfo* info = new SockInfo; |
| TcpSocket* tcp = s.acceptConn(&info->addr); |
| if (tcp == nullptr) |
| { |
| cout << "重试...." << endl; |
| continue; |
| } |
| |
| pthread_t tid; |
| info->s = &s; |
| info->tcp = tcp; |
| |
| pthread_create(&tid, NULL, working, info); |
| pthread_detach(tid); |
| } |
| |
| return 0; |
| } |
# client.cpp
| #include "TcpSocket.h" |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <fcntl.h> |
| int main() |
| { |
| |
| TcpSocket tcp; |
| |
| |
| int ret = tcp.connectToHost("192.168.217.1", 10000); |
| if (ret == -1) |
| { |
| return -1; |
| } |
| |
| |
| int fd1 = open("english.txt", O_RDONLY); |
| int length = 0; |
| char tmp[100]; |
| memset(tmp, 0, sizeof(tmp)); |
| while ((length = read(fd1, tmp, sizeof(tmp))) > 0) |
| { |
| |
| tcp.sendMsg(string(tmp, length)); |
| |
| cout << "send Msg: " << endl; |
| cout << tmp << endl << endl << endl; |
| memset(tmp, 0, sizeof(tmp)); |
| |
| |
| usleep(300); |
| } |
| |
| sleep(10); |
| |
| return 0; |
| } |
# 运行结果