1、创建套接字 #include <sys/socket.h> int socket(int domain, int type, int protocol); 返回值:成功返回值套接字的文件描述符 失败 -1 参数:domain -->地址协议类型,常用的有: AF_INET或者PF_INET -->ipv4地址 AF_INET6或者PF_INET6 -->ipv6地址 AF_UNIX或者PF_UNIX或者AF_LOCAL -->unix本地域套接字 type -->你创建的套接字类型 tcp套接字(流式套接字/数据流套接字) --> SOCK_STREAM udp套接字(数据报套接字) --> SOCK_DGRAM protocol -->扩展协议,默认设置为0 2、绑定ip和端口号 #include <sys/types.h> #include <sys/socket.h> int bind(int socket, const struct sockaddr *address,socklen_t address_len); 返回值:成功 0 失败 -1 参数:socket-->套接字 struct sockaddr--> 通用地址结构体,兼容ipv4和ipv6 { sa_family_t sa_family;-->存放地址协议类型 char sa_data[14];-->存放ip和端口号 } struct sockaddr_in --> ipv4地址结构体 { unsigned short sin_family; -->存放地址协议类型AF_INET或者AF_INET6 unsigned short int sin_port; -->端口号 struct in_addr sin_addr; -->绑定自己的ip地址 struct in_addr { in_addr_t s_addr; } unsigned char sin_zero; -->充数的,打酱油的,为了跟通用地址结构体大小保持一致 } struct sockaddr_in6 -->ipv6地址结构体 { sa_family_t sin6_family; // 地址族 u_int16_t sin6_port; // 端口号 struct in6_addr sin6_addr; // IPv6地址结构体 struct in6_addr { unsigned char sa_addr[16]; // IPv6地址 }; u_int32_t sin6_flowinfo; // 流信息 u_int32_t sin6_scope_id; // scope ID }; address_len -->地址结构体的大小 3、ip和端口号的转换 大小端: 大端序:数据的高字节存放在低地址,低字节存放在高地址 小端序:数据的高字节存放在高地址,低字节存放在低地址 ubuntu系统:小端序存放,称之为主机字节序 网络上的数据:大端序存放,称之为网络字节序 将主机字节序转换成网络字节序 转换ip: #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> in_addr_t inet_addr(const char *cp); 返回值:大端序ip 失败 -1 参数:cp -->小端序ip,点分十进制格式的ip int inet_aton(const char *cp, struct in_addr *inp); 转换端口号: uint32_t htonl(uint32_t hostlong);//某些特定情况下转换ip uint16_t htons(uint16_t hostshort); uint32_t ntohl(uint32_t netlong); 规律:h -->host n -->network l -->long 跟ip转换有关 s -->short 跟端口转换有关 将网络字节序转换成主机字节序 转换ip: char *inet_ntoa(struct in_addr in); const char *inet_ntop(int af, const void *src,char *dst, socklen_t size); 参数:af -->地址协议 AF_INET src -->存放你要转换的网络字节序ip dst -->存放转换好的主机字节序ip size -->ip地质大小 转换端口号: uint16_t ntohs(uint16_t netshort); 4、连接 #include <sys/types.h> #include <sys/socket.h> int connect(int socket, const struct sockaddr *address, socklen_t address_len); 参数:address -->存放对方的ip和端口号 address_len -->地址结构体的大小 5、监听(服务器端) int listen(int socket, int backlog); 参数:backlog -->同时能够接受的最大客户端连接数量 listen(tcpsock,7); 6、接受连接请求(服务器端) int accept(int socket, struct sockaddr *restrict address,socklen_t *restrict address_len); 返回值:成功 新的套接字用于等一会收发信息 失败 -1(要产生新的套接字,主要是为了区分不同客户端的连接) 参数:address -->存放连接服务器的那个客户端的ip和端口 address_len -->地址结构体的大小 特点:在没有客户端连接服务器的时候,服务器阻塞在accept(主要是阻塞在socket上) 当有客户端成功连接服务器的时候,会解除accept阻塞,产生新套接字 7、收发信息 ssize_t send(int socket, const void *buffer, size_t length, int flags); 返回值:length是多少,就返回多少 参数:flags -->默认设置0 length-->发送字节数 ssize_t recv(int socket, void *buffer, size_t length, int flags); 返回值:跟send有关 参数:flags -->默认设置0 -->MSG_WAITALL -->recv会一直等待length个字节的数据全部接收完毕才退出 三种情况 情况一: 成功 大于0 情况二: 等于0,表示客户端或者服务器断开连接了 情况三: -1 失败 特点:正常情况下,收不到消息阻塞,但是如果客户端或者服务器断开连接,不阻塞,然后返回0 8、遇到的问题 问题一: 绑定失败!: Address already in use 原因:你退出程序的时候,使用的端口号并不是立马就释放掉了,会延时一段时间(不同系统,时间不一样),导致你再次运行程序,提示绑定失败 解决方案: 第一种:更换新的端口号,通过主函数传参更换 第二种:取消端口号绑定限制(设置套接字的属性) int setsockopt(int socket,int level,int option_name,void *option_value,socklen_t option_len); 参数:level -->SOL_SOCKET option_name -->SO_REUSEADDR //取消端口号绑定限制 -->SO_BROADCAST //设置udp广播 option_value option_len int on=1; 例如: setsockopt(tcpsock,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)); 问题二: 服务器突然断开,导致客户端疯狂打印,不阻塞了 原因:recv正常情况下,收不到消息阻塞,但是如果客户端或者服务器断开连接,不阻塞,然后返回0
1、客户端
//单向一次性通信 #include <sys/types.h> #include <sys/socket.h> #include <stdio.h> #include <netinet/in.h> #include <arpa/inet.h> int main() { int tcpsock; char buf[50]={0}; int ret; //定义ipv4地址结构体变量 struct sockaddr_in bindaddr; bzero(&bindaddr,sizeof(bindaddr)); bindaddr.sin_family=AF_INET; bindaddr.sin_port=htons(10000);//自己指定了一个端口 bindaddr.sin_addr.s_addr=inet_addr("192.168.22.131");//绑定自己的ip地址,将小端序ip转换成大端序 //方法二 inet_aton("192.168.22.9",&(bindaddr.sin_addr)); //定义ipv4地址结构体变量存放你要连接的服务器的ip和端口 struct sockaddr_in serveraddr; bzero(&serveraddr,sizeof(serveraddr)); serveraddr.sin_family=AF_INET; serveraddr.sin_port=htons(20000);//服务器的端口号 serveraddr.sin_addr.s_addr=inet_addr("192.168.22.22");//服务器的ip //创建tcp类型的套接字 tcpsock=socket(AF_INET,SOCK_STREAM,0); if(tcpsock==-1)//if(-1==tcpsock) { perror("创建套接字!\n"); return -1; } //绑定ip和端口号 ret=bind(tcpsock,(struct sockaddr *)&bindaddr,sizeof(bindaddr)); if(ret==-1) { perror("绑定失败!\n"); return -1; } //连接服务器 ret=connect(tcpsock,(struct sockaddr *)&serveraddr,sizeof(serveraddr)); if(ret==-1) { perror("连接服务器失败!\n"); return -1; } //聊天 printf("请输入要发送给服务器的信息!\n"); scanf("%s",buf); ret=send(tcpsock,buf,50,0); printf("send的返回值是:%d\n",ret); //挂机 close(tcpsock); return 0; }2、服务器端
pthread_t id; //线程ID号 int newsock;//连上新的客户端产生的新套接字 void *recv1(void *arg) //开一个线程来回复信息 { int ret; char buf[50]={0}; while(1) { printf("请输入要发送给客户端的信息!\n"); scanf("%s",buf); ret=send(newsock,buf,50,0); if(strcmp(buf,"quit")==0) exit(-1); } } int main() { char buf[50]={0}; char ipbuf[20]={0}; int tcpsock; int ret; //定义ipv4地址结构体变量 struct sockaddr_in bindaddr; bzero(&bindaddr,sizeof(bindaddr)); bindaddr.sin_family=AF_INET; bindaddr.sin_port=htons(20000);//自己指定了一个端口 bindaddr.sin_addr.s_addr=inet_addr("192.168.22.124");//绑定自己的ip地址,将小端序ip转换成大端序 //定义ipv4地址结构体变量存放连接成功的那个客户端的ip和端口号 struct sockaddr_in clientaddr; bzero(&clientaddr,sizeof(clientaddr)); int addrsize=sizeof(clientaddr); //创建tcp类型的套接字 tcpsock=socket(AF_INET,SOCK_STREAM,0); if(tcpsock==-1)//if(-1==tcpsock) { perror("创建套接字!\n"); return -1; } //绑定ip和端口号 ret=bind(tcpsock,(struct sockaddr *)&bindaddr,sizeof(bindaddr)); //监听 ret=listen(tcpsock,7); if(ret==-1) { perror("监听失败!\n"); return -1; } pthread_create(&id,NULL,recv1,NULL); //创建子线程进行收信息 //接收信息 while(1) { newsock=accept(tcpsock,(struct sockaddr *)&clientaddr,&addrsize); //接受连接请求 if(newsock==-1) { perror("接受连接请求失败!\n"); return -1; } //打印当前连接成功的那个客户端的ip跟端口号 printf("目前连接成功的客户端ip是:%s 端口号是:%hu\n",inet_ntop(AF_INET,&(clientaddr.sin_addr),ipbuf,20),ntohs(clientaddr.sin_port)); ret=recv(newsock,buf,50,0); printf("收到的信息是:%s recv返回值是:%d\n",buf,ret); if(strcmp(buf,"quit")==0) break; } //recv(tcpsock,buf,50,0); //挂机 close(tcpsock); close(newsock); return 0; }3、TCP广播和点播(使用多线程和单链表)