什么是Socket
在计算机通信领域,socket 被翻译为“套接字”,它是计算机之间进行通信的一种约定或一种方式。通过 socket 这种约定,一台计算机可以接收其他计算机的数据,也可以向其他计算机发送数据
socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。
我的理解就是Socket就是该模式的一个实现:即socket是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)。
Socket()函数返回一个整型的Socket描述符,随后的连接建立、数据传输等操作都是通过该Socket实现的。
Socket基础
首先看一个基于TCP的CS案例,客户端连接到服务器后,收到服务器发来的消息并打印,为步骤清晰,去掉了一些差错判断。
Server
| #include <iostream> |
| #include <sys/socket.h> |
| #include <netinet/in.h> |
| #include <arpa/inet.h> |
| #include <unistd.h> |
| |
| int setAddr(sockaddr_in * addr,short port,const char * ip){ |
| memset(addr, 0, sizeof(sockaddr_in)); |
| addr->sin_family = AF_INET; |
| addr->sin_addr.s_addr = inet_addr(ip); |
| addr->sin_port = htons(port); |
| return 0; |
| } |
| |
| int main() { |
| |
| sockaddr_in addr{}; |
| setAddr(&addr,1234,"127.0.0.1"); |
| |
| |
| int fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); |
| |
| |
| if(0!=bind(fd, (struct sockaddr *) &addr, sizeof(addr))){ |
| std::cout << "bind failed!" << std::endl; |
| } |
| |
| |
| if(0!=listen(fd, 20)){ |
| std::cout << "listen failed!" << std::endl; |
| } |
| |
| |
| sockaddr_in clientAddr{}; |
| socklen_t clientAddrLen = sizeof(clientAddr); |
| int clientFd = accept(fd, (struct sockaddr*)&clientAddr, &clientAddrLen); |
| |
| |
| char str[] = "Welcome!"; |
| write(clientFd, str, sizeof(str)); |
| |
| |
| close(clientFd); |
| close(fd); |
| |
| return 0; |
| } |
| |
Client
| #include <iostream> |
| #include <sys/socket.h> |
| #include <netinet/in.h> |
| #include <arpa/inet.h> |
| #include <unistd.h> |
| int setAddr(sockaddr_in * addr,short port,const char * ip){ |
| memset(addr, 0, sizeof(sockaddr_in)); |
| addr->sin_family = AF_INET; |
| addr->sin_addr.s_addr = inet_addr(ip); |
| addr->sin_port = htons(port); |
| return 0; |
| } |
| |
| int main() { |
| |
| |
| sockaddr_in addr{}; |
| |
| setAddr(&addr,1234,"127.0.0.1"); |
| |
| |
| int fd = socket(AF_INET, SOCK_STREAM, 0); |
| |
| |
| if(0!=connect(fd,(struct sockaddr*)&addr,sizeof(addr) ) ){ |
| std::cout<<"connect failed!"<<std::endl; |
| } |
| |
| |
| char buffer[40]; |
| read(fd, buffer, sizeof(buffer)-1); |
| printf("%s\n", buffer); |
| |
| |
| close(fd); |
| return 0; |
| } |
| |
相关结构
| typedef unsigned char __uint8_t; |
| typedef __uint8_t sa_family_t; |
| typedef __uint16_t in_port_t; |
| typedef __uint32_t in_addr_t; |
| |
| struct sockaddr { |
| __uint8_t sa_len; |
| sa_family_t sa_family; |
| char sa_data[14]; |
| }; |
| |
| struct in_addr { |
| in_addr_t s_addr; |
| }; |
| |
| struct sockaddr_in { |
| __uint8_t sin_len; |
| sa_family_t sin_family; |
| in_port_t sin_port; |
| struct in_addr sin_addr; |
| char sin_zero[8]; |
| }; |
相关函数
socket
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| int socket(int domain, int type, int protocol); |
bind
| |
| |
| |
| |
| |
| |
| |
| int bind(int socket, const struct sockaddr * addr, socklen_t) |
| |
listen
| |
| |
| |
| |
| |
| |
| |
| int listen(int socket, int backlog); |
accept
| |
| |
| |
| |
| |
| |
| |
| |
| int accept(int socket, struct sockaddr *restrict_address, socklen_t *restrict_address_len); |
read & write
| |
| |
| |
| |
| ssize_t read(int fd, void * buffer, size_t buffer_size) ; |
| ssize_t write(int fd, const void * buffer, size_t buffer_size) |
| |
close
recv & send
上述例子中使用了read和write函数对socket进行读取和写入数据,事实上,操作socket的数据有更好的方式,即使用recv函数和send函数。
send
| #include <sys/socket.h> |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| ssize_t send(int socket, const void *buffer, size_t length, int flags); |
| |
recv
| #include <sys/socket.h> |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| ssize_t recv(int socket, void *buffer, size_t length, int flags); |
| |
除此之外,还有
| ssize_t sendmsg(int socket, const struct msghdr *message, int flags); |
| ssize_t recvmsg(int socket, struct msghdr *message, int flags); |
| |
| ssize_t sendto(int socket, const void *buffer, size_t length, int flags, |
| const struct sockaddr *dest_addr, socklen_t dest_len); |
| ssize_t recvfrom(int socket, void *restrict buffer, size_t length, int flags, |
| struct sockaddr *restrict address, socklen_t *restrict address_len); |
关于read/write,recv/send,recvmsg/sendmsg,recvfrom/sendto的区别
- read/write:通用文件描述符操作函数
- send:发送消息,send只可用于基于连接的套接字,send 和 write唯一的不同点是标志的存在,当标志为0时,send等同于write。
- sendto 和 sendmsg:既可用于无连接的套接字,也可用于基于连接的套接字;除了套接字设置为非阻塞模式,调用将会阻塞直到数据被发送完。
- recv:读取消息,一般只用在面向连接的套接字。
- recvfrom和recvmsg:可同时应用于面向连接的和无连接的套接字。
使用示例参考后文。
Example - Tcp Echo Server
基于有连接的网络通信,使用send和recv进行数据的收发
recv
| char buffer[4096]; |
| ssize_t bytes = recv(cfd, (void *)&buffer, 4096, 0); |
send
| char buffer[4096]{"test!"}; |
| send(client_fd, &buffer, strlen(buffer), 0) |
Server
| #include <iostream> |
| #include <netinet/in.h> |
| #include <sys/socket.h> |
| #include <unistd.h> |
| #include <arpa/inet.h> |
| |
| int main(int , char **) { |
| |
| int fd = socket(AF_INET, SOCK_STREAM, 0); |
| if (fd < 0) { |
| std::cerr << "Socket creat failed!"<<std::endl; |
| return -2; |
| } |
| |
| sockaddr_in server_addr{}; |
| server_addr.sin_family = AF_INET; |
| server_addr.sin_port = htons(1234); |
| server_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); |
| |
| if (bind(fd, (sockaddr *)&server_addr, sizeof(server_addr)) < 0) { |
| std::cerr <<"bind error."<<std::endl; |
| return -3; |
| } |
| if (listen(fd, 20) < 0) { |
| std::cerr << "listen error."<<std::endl; |
| return -4; |
| } |
| |
| sockaddr_in client_addr{}; |
| socklen_t client_addr_size = sizeof(client_addr); |
| int cfd = accept(fd, (sockaddr*)&client_addr, &client_addr_size); |
| if (cfd < 0) { |
| std::cerr << "accept error"<<std::endl; |
| return -5; |
| } |
| close(fd); |
| |
| |
| char buffer[4096]; |
| ssize_t bytes; |
| while (true) { |
| bytes = recv(cfd, (void *)&buffer, 4096, 0); |
| if (bytes == 0) { |
| std::cout << "client disconnected."<<std::endl; |
| break; |
| } |
| else if (bytes < 0) { |
| std::cerr << "recv error."<<std::endl; |
| break; |
| } |
| else { |
| std::cout << "[client]" << std::string(buffer, 0, bytes) << std::endl; |
| if (send(cfd, &buffer, bytes, 0) < 0) { |
| std::cerr << "send error, exiting...\n"; |
| break; |
| } |
| } |
| } |
| close(cfd); |
| |
| return 0; |
| } |
Client
| #include <iostream> |
| #include <string> |
| #include <cstring> |
| #include <unistd.h> |
| #include <sys/socket.h> |
| #include <arpa/inet.h> |
| |
| int main(int , char **) { |
| |
| int fd = socket(AF_INET, SOCK_STREAM, 0); |
| if (fd < 0) { |
| std::cerr << "Socket creat error."<<std::endl; |
| return -2; |
| } |
| |
| sockaddr_in server_addr{}; |
| server_addr.sin_family = AF_INET; |
| server_addr.sin_port = htons(1234); |
| server_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); |
| |
| if (connect(fd, (sockaddr*)&server_addr, sizeof(server_addr)) < 0) { |
| std::cerr << "connect error."<<std::endl; |
| return -3; |
| } |
| |
| char buf[4096]; |
| std::string inputTemp; |
| while(true) { |
| std::memset(buf, 0, 4096); |
| std::cout << ">"; |
| std::getline(std::cin, inputTemp, '\n'); |
| std::strcpy(buf, inputTemp.c_str()); |
| |
| if (strlen(buf)==0) { |
| continue; |
| } |
| else if (strcmp(buf, "quit")==0) { |
| break; |
| } |
| |
| ssize_t bytes_send = send(fd, &buf, (size_t)strlen(buf), 0); |
| if (bytes_send < 0) { |
| std::cerr << "send error."<<std::endl; |
| break; |
| } |
| |
| ssize_t bytes_recv = recv(fd, &buf, 4096, 0); |
| if (bytes_recv < 0) { |
| std::cerr << "recv error."<<std::endl; |
| } |
| else if (bytes_recv == 0) { |
| std::cout << "server closed."; |
| } |
| else { |
| std::cout << "[server]" << std::string(buf, 0, bytes_recv) << std::endl; |
| } |
| } |
| close(fd); |
| return 0; |
| } |
Example - Udp Echo Server
基于无连接的网络通信,使用sendto和recvfrom收发消息
sendto和recvfrom也可以支持面向连接的网络通信
recvfrom
| ssize_t bytes_len = recvfrom(server_fd, buf, sizeof(buf), 0, (struct sockaddr *)&client_addr, &socklen); |
sendto
| ssize_t bytes_len = sendto(server_fd, buf, sizeof(buf), 0, (struct sockaddr *)&client_addr, socklen); |
Server
| #include <sys/socket.h> |
| #include <arpa/inet.h> |
| #include <unistd.h> |
| #include <iostream> |
| |
| int main() |
| { |
| sockaddr_in addr{}; |
| addr.sin_family = AF_INET; |
| addr.sin_addr.s_addr = inet_addr("127.0.0.1"); |
| addr.sin_port = htons(1234); |
| |
| int fd = socket(AF_INET, SOCK_DGRAM, 0); |
| if (fd < 0){ |
| return -1; |
| } |
| if (bind(fd, (const sockaddr *)&addr, sizeof(addr))<0){ |
| return -2; |
| } |
| char buf[1024]; |
| sockaddr_in client_addr{}; |
| socklen_t socklen = sizeof(client_addr); |
| |
| while (true) |
| { |
| ssize_t bytes_len = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&client_addr, &socklen); |
| if (bytes_len < 0){ |
| std::cout<<"recv failed"<<std::endl; |
| break; |
| } |
| else{ |
| std::cout<<"[client]"<<buf<<std::endl; |
| } |
| |
| bytes_len = sendto(fd, buf, sizeof(buf), 0, (struct sockaddr *)&client_addr, socklen); |
| if (bytes_len < 0){ |
| std::cout<<"send failed"<<std::endl; |
| break; |
| } |
| } |
| close(fd); |
| return 0; |
| } |
| |
Client
| #include <sys/socket.h> |
| #include <arpa/inet.h> |
| #include <unistd.h> |
| #include <iostream> |
| |
| int main() |
| { |
| sockaddr_in addr{}; |
| addr.sin_family = AF_INET; |
| addr.sin_addr.s_addr = inet_addr("127.0.0.1"); |
| addr.sin_port = htons(1234); |
| |
| int fd = socket(AF_INET, SOCK_DGRAM, 0); |
| if(fd < 0){ |
| return -1; |
| } |
| |
| ssize_t bytes_len ; |
| char buf[1024] = "hello world!"; |
| bytes_len = sendto(fd, buf, strlen(buf), 0, (sockaddr*)&addr, sizeof(addr)); |
| if(bytes_len < 0){ |
| std::cout<<"send failed"<<std::endl; |
| return -2; |
| } |
| bytes_len = recvfrom(fd, buf, strlen(buf), 0,nullptr, nullptr); |
| if(bytes_len < 0){ |
| std::cout<<"recv failed"<<std::endl; |
| return -3; |
| } |
| else{ |
| std::cout<<"[server]"<<buf<<std::endl; |
| } |
| close(fd); |
| return 0; |
| } |
| |
Example - Sendmsg and Recvmsg
基于无连接的网络通信,使用sendto和recvfrom收发消息
sendto和recvfrom也可以支持面向连接的网络通信
首先回顾一下recvmsg和sendmsg的函数原型
| struct iovec { |
| void * iov_base; |
| size_t iov_len; |
| }; |
| |
| struct msghdr { |
| void *msg_name; |
| socklen_t msg_namelen; |
| struct iovec *msg_iov; |
| int msg_iovlen; |
| void *msg_control; |
| socklen_t msg_controllen; |
| int msg_flags; |
| }; |
| |
| ssize_t sendmsg(int socket, const struct msghdr *message, int flags); |
| ssize_t recvmsg(int socket, struct msghdr *message, int flags); |
Basic Usage
详细参考:https://blog.csdn.net/weixin_45309916/article/details/107905942
| char buf[1024]; |
| sockaddr_in client_addr{}; |
| |
| struct iovec msg_iov[1]; |
| msg_iov[0].iov_base = buf; |
| msg_iov[0].iov_len = sizeof(buf); |
| |
| struct msghdr msg{}; |
| msg.msg_name = &client_addr; |
| msg.msg_namelen = sizeof(client_addr); |
| msg.msg_iov = msg_iov; |
| msg.msg_iovlen = 1; |
| msg.msg_control = nullptr; |
| msg.msg_controllen = 0; |
| msg.msg_flags = 0; |
| |
| ssize_t bytes_len; |
| |
| bytes_len = sendmsg(fd, &msg, 0); |
| bytes_len = recvmsg(fd, &msg, 0); |
Server
| #include <sys/socket.h> |
| #include <arpa/inet.h> |
| #include <unistd.h> |
| #include <iostream> |
| int main() |
| { |
| sockaddr_in addr{}; |
| addr.sin_family = AF_INET; |
| addr.sin_addr.s_addr = inet_addr("127.0.0.1"); |
| addr.sin_port = htons(1234); |
| |
| int fd = socket(AF_INET, SOCK_DGRAM, 0); |
| if (fd < 0){ |
| return -1; |
| } |
| if (bind(fd, (const sockaddr *)&addr, sizeof(addr))<0){ |
| return -2; |
| } |
| char buf[1024]; |
| sockaddr_in client_addr{}; |
| |
| iovec msg_iov[1]; |
| msg_iov[0].iov_base = buf; |
| msg_iov[0].iov_len = sizeof(buf); |
| |
| msghdr msg{}; |
| msg.msg_name = &client_addr; |
| msg.msg_namelen = sizeof(client_addr); |
| msg.msg_iov = msg_iov; |
| msg.msg_iovlen = 1; |
| msg.msg_control = nullptr; |
| msg.msg_controllen = 0; |
| msg.msg_flags = 0; |
| |
| while (true) |
| { |
| ssize_t bytes_len = recvmsg(fd,&msg,0); |
| if(bytes_len < 0){ |
| std::cout<<"recv failed"<<std::endl; |
| break; |
| } |
| else{ |
| std::cout<<"[client]"<<buf<<std::endl; |
| } |
| |
| |
| memset(buf,0,sizeof(buf)); |
| strcpy(buf,"Hi,Im server!"); |
| |
| bytes_len = sendmsg(fd, &msg, 0); |
| if (bytes_len < 0){ |
| std::cout<<"send failed"<<std::endl; |
| break; |
| } |
| } |
| close(fd); |
| return 0; |
| } |
| |
Client
| #include <sys/socket.h> |
| #include <arpa/inet.h> |
| #include <unistd.h> |
| #include <iostream> |
| |
| int main() |
| { |
| sockaddr_in addr{}; |
| addr.sin_family = AF_INET; |
| addr.sin_addr.s_addr = inet_addr("127.0.0.1"); |
| addr.sin_port = htons(1234); |
| |
| int fd = socket(AF_INET, SOCK_DGRAM, 0); |
| if(fd < 0){ |
| return -1; |
| } |
| |
| ssize_t bytes_len ; |
| char buf[1024] = "hello world!"; |
| |
| iovec msg_iov{}; |
| msg_iov.iov_base = buf; |
| msg_iov.iov_len = sizeof(buf); |
| |
| msghdr msg{}; |
| msg.msg_name = &addr; |
| msg.msg_namelen = sizeof(addr); |
| msg.msg_iov = &msg_iov; |
| msg.msg_iovlen = 1; |
| msg.msg_control = nullptr; |
| msg.msg_controllen = 0; |
| msg.msg_flags = 0; |
| |
| bytes_len = sendmsg(fd, &msg, 0); |
| if(bytes_len < 0){ |
| std::cout<<"send failed"<<std::endl; |
| return -2; |
| } |
| |
| bytes_len = recvmsg(fd,&msg,0); |
| if(bytes_len < 0){ |
| std::cout<<"recv failed"<<std::endl; |
| return -3; |
| } |
| else{ |
| std::cout<<"[server]"<<buf<<std::endl; |
| } |
| close(fd); |
| return 0; |
| } |
| |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步