套接字Socket——TCP、UDP通信
套接字(socket:插座)
是一种可以进行网络通信的内核对象,它有一个唯一的标识符,一般称它为socket描述符,跟文件描述符类似,也可以用read/write/close操作
功能:创建socket对象,返回socket描述符
domain:通信地址类型,AF_UNIX (本地进程间通信) 、AF_INET (使用ipv4地址通信) 、AF_INET6 (使用ipv6地址通信);
type:
SOCK_STREAM:数据流协议,TCP面向连接的通信协议,优点是安全可靠,数据不会丢失,但速度慢。一般常用于安全性较高的场景。
SOCK_DGRAM:数据报协议,UDP面向无连接的通信协议,优点是速度快,数据可能丢失,安全性和可靠性与tcp相比不同。一般用于安全性要求不高但是对速度有要求的场景。
protocol:通常是0,表示为给定的域和套接字类型选择默认协议。
准备通信地址:
本地通信地址
struct sockaddr_un
{
sun_family_t sun_family; // 通信地址类型,AF_UNIX、AF_INET、AF_INET6等
char sun_path[108]; // socket文件的路径,本地通信的socket文件
};
网络通信地址
struct sockaddr_in
{
short int sin_family; // 通信地址类型,AF_UNIX、AF_INET、AF_INET6等
in_port_t sin_port; // 端口号,以网络字节序表示的16位整数
struct in_addr sin_addr; // ip地址,32位的无符号整数
}
服务器绑定socket与通信地址:
一个socket对象只能绑定一个地址。
功能:把socket对象与通信地址建立联系
sockfd:socket函数返回的socket描述符
addr:通信地址
addrlen:通信地址的长度
客户端连接通信目标:
个人计算机系统数据的存储方式可能是大端,也可能是小端,网络通信时需要的是大端数据,必须把数据转换成大端。
uint32_t htonl(uint32_t hostlong); 功能:把32位的主机字节序转换成32位网络字节序
uint16_t htons(uint16_t hostshort); 功能:把16位的主机字节序转换成16位网络字节序
uint32_t ntohl(uint32_t netlong); 功能:把32位网络字节序转换成32位的主机字节序
uint16_t ntohs(uint16_t netshort); 功能:把16位网络字节序转换成16位的主机字节序
生成端口号:
端口号就是一个16位的无符整数因此:uint16_t htons(uint16_t hostshort);
生成ip地址:in_addr_t inet_addr(const char *cp);
功能:把点分十进制的字符串ip地址转换成32位的无符号整数
char *inet_ntoa(struct in_addr in);
功能:把32的的网络字节序的ip地址转换成点分十进制的字符串ip地址。
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
功能:接收数据并获取发送端的地址
如果addr非空,它将包含数据发送者的套接字端点地址。当调用recvfrom时,需要设置addrlen参数指向一个整数,该整数包含addr所指向的套接字缓冲区的字节 长度。返回时,该整数设为该地址的实际字节长度。因为可以获得发送者的地址,recvfrom通常用于无连接的套接字。否则,recvfrom等同于recv
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
功能:发送数据到指定的目标,面对无连接的套接字。
当socket对象被全部关闭,会在内核中停留一段时间(会给重新连接的机会),如果再使用同样的ip地址和端口号时就会失败(延时关闭)。
示例:
udp_client
#include <stdio.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include <unistd.h> typedef struct sockaddr* sockaddrp; int main() { // 创建socket对象 int sockfd = socket(AF_INET,SOCK_DGRAM,0); if(0 > sockfd) { perror("socket"); return -1; } // 准备通信地址 struct sockaddr_in addr = {AF_INET}; addr.sin_port = htons(6666); addr.sin_addr.s_addr = inet_addr("127.0.0.1"); while(1) { char buf[255] = {}; // 发送数据到目标 printf("请输入要返回的数据:"); gets(buf); sendto(sockfd,buf,strlen(buf),0,(sockaddrp)&addr,sizeof(addr)); if(0 == strcmp(buf,"q")) break; // 接收数据与来时的路径 socklen_t addr_len; recvfrom(sockfd,buf,sizeof(buf),0,(sockaddrp)&addr,&addr_len); printf("接收到数据:%s\n",buf); if(0 == strcmp(buf,"q")) break; } // 关闭 close(sockfd); }
udp_server
#include <stdio.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include <unistd.h> typedef struct sockaddr* sockaddrp; int main() { // 创建socket对象 int sockfd = socket(AF_INET,SOCK_DGRAM,0); if(0 > sockfd) { perror("socket"); return -1; } // 准备通信地址 struct sockaddr_in addr = {AF_INET}; addr.sin_port = htons(6666); addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 绑定对象与地址 int ret = bind(sockfd,(sockaddrp)&addr,sizeof(addr)); if(0 > ret) { perror("bind"); return -1; } struct sockaddr_in src_addr = {}; socklen_t addr_len = sizeof(addr); while(1) { char buf[255] = {}; // 接收数据与来时的路径 recvfrom(sockfd,buf,sizeof(buf),0,(sockaddrp)&src_addr,&addr_len); printf("接收到数据:%s\n",buf); if(0 == strcmp(buf,"q")) break; // 发送数据到目标 printf("请输入要返回的数据:"); gets(buf); ret = sendto(sockfd,buf,strlen(buf),0,(sockaddrp)&src_addr,addr_len); if(0 == strcmp(buf,"q")) break; } // 关闭 close(sockfd); }
面向连接的通信TCP
功能:声明sockfd处于监听状态,成功返回0,失败返回-1
sockfd:socket函数返回的socket描述符
backlog:提示系统该进程所要入队的未完成连接请求数量。一旦队列满,系统就会拒绝多余的连接请求。
功能:当有客户端发起连接时,服务器就调用accept()返回并接收这个连接,如果有大量客户端发起请求,服务器来不及处理,还没有accept的客户端就处于连接等待状态。
addr:准备的通信地址 addrlen:通信地址长度
类似于文件操作的read/write,只是多了一个flags标志位,通常填写0,表示为默认的阻塞操作,如果指定为MSG_DONTWAIT则允许非阻塞操作
tcp_client
#include <stdio.h> #include <sys/socket.h> #include <sys/types.h> #include <arpa/inet.h> #include <unistd.h> #include <stdlib.h> #include <stdbool.h> #include <string.h> #include <netinet/in.h> typedef struct sockaddr* sockaddrp; int main() { int sockfd = socket(AF_INET,SOCK_STREAM,0); struct sockaddr_in addr = {AF_INET}; addr.sin_port = htons(6666); addr.sin_addr.s_addr = inet_addr("127.0.0.1"); connect(sockfd,(sockaddrp)&addr,sizeof(addr)); while(1) { char buf[255] ={}; printf("请输入要发送的数据:"); gets(buf); send(sockfd,buf,strlen(buf),0); if(0 == strcmp("q",buf)) break; bzero(buf,sizeof(buf)); int ret = recv(sockfd,buf,sizeof(buf),0); printf("读取到%d字节,内容:%s\n",ret,buf); if(0 == strcmp("q",buf)) break; } close(sockfd); }
tcp_server
#include <stdio.h> #include <sys/socket.h> #include <sys/types.h> #include <arpa/inet.h> #include <unistd.h> #include <stdlib.h> #include <stdbool.h> #include <string.h> #include <netinet/in.h> typedef struct sockaddr* sockaddrp; int main() { //建立socket对象 int sockfd = socket(AF_INET,SOCK_STREAM,0); if(sockfd < 0) { perror("socket"); return -1; } //准备通讯地址 struct sockaddr_in addr = {AF_INET}; addr.sin_port = htons(6666); addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //绑定对象与地址 int ret = bind(sockfd,(sockaddrp)&addr,sizeof(addr)); if(ret < 0) { perror("bind"); return -1; } //设置排队数量 listen(sockfd,1024); //等待连接 struct sockaddr_in src_addr = {}; socklen_t addr_len = sizeof(src_addr); bool flag = true; while(flag) { int clifd = accept(sockfd,(sockaddrp)&src_addr,&addr_len); //通信 while(1) { char buf[255] = {}; ret = recv(clifd,buf,sizeof(buf),0); printf("接收到%d字节数据,内容:%s\n",ret,buf); if(0 == strcmp("q",buf)) { flag = false; break; } printf("请输入返回内容:"); gets(buf); send(clifd,buf,strlen(buf),0); if(0 == strcmp("q",buf)) { flag = false; break; } } close(clifd); } //关闭socket close(sockfd); }