socket编程——TCP协议(C语言代码实现)

xiaoxiao2025-07-17  14

Linux系统下socket编程 ——TCP协议

 一、相关API

      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广播和点播(使用多线程和单链表) 

 

转载请注明原文地址: https://www.6miu.com/read-5033256.html

最新回复(0)