【网络\Linux】Linux网络编程-TCP,UDP套接字编程及代码示范


本文我们分为TCP和UDP协议网络程序的编写:
他们的区别如下:

项目特点
UDP用户数据报协议,无需连接,不可靠,面向数据报,其实时性>可靠性(典型的就是视频传输)
TCP传输控制协议,面向连接,可靠,面向字节流,其可靠性>实时性(文件传输)

UDP类

  1 #include <cstdio>                                                                                                                                                                     
  2 #include <iostream>
  3 #include <string>
  4 #include <unistd.h>
  5 #include <arpa/inet.h>
  6 #include <netinet/in.h>
  7 #include <sys/socket.h>
  8 //dup网络协议类
  9 //需要实现5个接口,
 10 //1.创建套接字
 11 //2.绑定地址信息(一般是服务器端需要)
 12 //3.发送数据
 13 //4.接受数据
 14 class UDPSocket{
 15 //套接字的成员变量就是套接字操作句柄
 16   private:
 17     int _sockfd;
 18 
 19   public:
 20 
 21     UDPSocket()
 22       :_sockfd(-1){}
 23     //创建套接字
 24     //socket(地址域类型,套接字类型,本次通信协议)
 25     bool Socket(){
 26     _sockfd=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
 27       if(_sockfd<0){
 28         perror("socket error");
 29         return false;
 30       }
 31       return true;
 32     }
 33 
 34 
 35   //绑定地址信息(套接字操作句柄,addr结构体地址信息指针,地址信息长度)
 36   //不同的通讯协议地址信息大小不同
 37   //统一的接口是sockaddr(为了接口规范)16字节
 38   //ipv4是sockaddr_in 16字节
 39   //封装的接口需要ip地址和port值
 40     bool Bind(const std::string &ip,uint16_t port){
 41       struct sockaddr_in addr;
 42       addr.sin_family=AF_INET;
 43       addr.sin_port=htons(port);
 44       addr.sin_addr.s_addr=inet_addr(ip.c_str());
 45       socklen_t len=sizeof(struct sockaddr_in);
 46       int ret;
 47       //第二参数要穿地址!!
 48       ret=bind(_sockfd,(struct sockaddr*)&addr,len);
 49       if(ret<0){
 50         perror("bind error");
 51         return false;
 52       }
 53       return true;
 54     }
 55     
 56 
 57     //发送数据接口
 58     //(套接字操作句柄,发送空间首地址,数据长度,0阻塞,对端地址信息,对端地址结构长度)
 59    //封装的接口要数据的地址信息,对端ip和port
 60    //注意ip是const,可以直接以"192.0.0.0"格式发送,否则只能是string类型
 61    //因为"192.0.0.0"是常量字符串
 62     bool Send(std::string& data,const std::string &ip,u_int16_t port){
 63       struct sockaddr_in addr;
 64       addr.sin_family=AF_INET;                                                                                                                                                         
 65       addr.sin_port=htons(port);
 66       addr.sin_addr.s_addr=inet_addr(ip.c_str());
 67       socklen_t len=sizeof(struct sockaddr_in);
 68       int ret=sendto(_sockfd,data.c_str(),data.size(),0,(sockaddr*)&addr,len);
 69       if(ret<0){
 70         perror("sendto error");
 71         return false;
 72       }
 73       return true;
 74     }
 75 
 76   //接受数据(操作句柄,接受空间地址,接收数据长度,0阻塞,(输出参数)发送源端地址信息,(输入输出型参数)指定接收的地质结构大小,返回实际的大小)   
 77   ///封装的接口是接受地址,接收发送方ip和port的地址(一般情况可以不接受,指定为NULL表示不需要直到发送方的信息)
 78     bool Recv(std::string * buf,std::string* ip=NULL,int * port=NULL){
 79       struct sockaddr_in addr;
 80       socklen_t len=sizeof(struct sockaddr_in);
 81       char tmp[1024]={0};
 82       int ret=recvfrom(_sockfd,tmp,1024,0,(sockaddr*)&addr,&len);
 83       if(ret<0){
 84         perror("sendto error");
 85             return false;                    
 86       }
 87       //ret为世界接收到的数据长度
 88       buf->assign(tmp,ret);
 89 
 90       if(ip!=NULL){
 91         *ip=inet_ntoa(addr.sin_addr);
 92       }
 93       if(port!=NULL){
 94         *port=ntohs(addr.sin_port);
 95       }
 96       return true;
 97     }
 98 
 99     //关闭套接字
100     bool Close(){
101       if(_sockfd!=-1){
102         close(_sockfd);
103       }
104       return true;
105     }
106                                                                                                                                                                                        
107 };

UDP服务端单执行流

  1 #include"classudp.hpp"
  2 
  3 #define CHECK_RET(q) if((q)==false){return -1;}
  4 //服务端需要做的步骤:
  5 //1.创建套接字
  6 //2.绑定地址信息
  7 //3.接收数据,(如果需要反馈给客户端则需要记录地址信息)
  8 //4.发送数据
  9 int main(){
 10   UDPSocket ssock;
 11   CHECK_RET(ssock.Socket());
 12   CHECK_RET(ssock.Bind("192.168.107.128",9000));
 13   while(1){
 14     std::string buf;
 15     int cliport;
 16     std::string clip;
 17     CHECK_RET(ssock.Recv(&buf,&clip,&cliport));
 18     std::cout<<"client say: "<<buf<<std::endl;
 19     buf.clear();//清空缓冲区                                                                                                                                                           
 20     std::cout<<"server say: ";
 21     std::cin>>buf;
 22     CHECK_RET(ssock.Send(buf,clip,cliport));
 23   }
 24   ssock.Close();
 25   return 0;
 26 }

UDP客户端

  1 #include "classudp.hpp"
  2 
  3 #define CHECK_RET(q) if((q)==false){return -1;}
  4 //客户端的任务流程
  5 //1.创建套接字
  6 //2.绑定地址结构
  7 //3.发送数据
  8 //4.接受数据
  9 //5.关闭套接字
 10 int main(){
 11 //1.
 12   UDPSocket sock;
 13   CHECK_RET(sock.Socket());
 14 //2.
 15 //3.
 16   while(1){
 17     std::cout<<"client saY:";
 18     std::string buf;
 19     std::cin>>buf;
 20     CHECK_RET(sock.Send(buf,"192.168.107.128",9000));
 21 
 22 
 23 //4.
 24     buf.clear();//不用的缓冲区清空
 25     CHECK_RET(sock.Recv(&buf));//用发送缓冲区接收,不接收源端信息就不写
 26     std::cout<<"server say: "<<buf<<std::endl;
 27   }
 28 //5.
 29   sock.Close();
 30   return 0;                                                                                                                                                                            
 31 }

TCP类

    1 #include <cstdio>
    2 #include <iostream>
    3 #include <string>                                                                                                                                                                                            
    4 #include <unistd.h>
    5 #include <arpa/inet.h>
    6 #include <netinet/in.h>
    7 #include <sys/socket.h>
    8 
    9 #define LISTEN_MAX 5
   10 #define CHECK_RET(q) if((q)==false){return -1;}
   11 //tcp的流程大致如下“
   12 //客户端:创建套接字,不推荐绑定地址信息,向服务器发送链接请求,收发数据,关闭套接字
   13 //服务端:创建套接字,绑定地址信息,开始监听,获取新建连接,收发数据,关闭套接字
   14 class TCPSocket{
   15   private:
   16     int _sockfd;
   17   public:
   18     TCPSocket():_sockfd(-1){}
   19     //创建套接字
   20     //socket(地址域类型,套接字类型,通信协议)
   21     //封装的接口需要获取操作句柄
   22     bool Socket(){
   23       _sockfd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
   						//IPV4,流传输协议,TCP
   24       if(_sockfd<0){
   25         perror("socket error");
   26         return false;
   27       }
   28       return true;
   29     }
   30 
   31     //绑定地址信息
   32     //接口(句柄,规范接口struct结构体,结构体大小)、
   33     //封装的接口(ip地址,port信息)
   34     bool Bind(const std::string &ip,const uint16_t port){
   35       sockaddr_in addr;
   36       addr.sin_family = AF_INET;//16位地址类型
   37       addr.sin_port = htons(port);
   38 
   39       //这两种都能拿到ip内容(str的首地址)
   40       addr.sin_addr.s_addr = inet_addr(&ip[0]);
   41       //addr.sin_addr.s_addr = inet_addr(ip.c_str());
   42 
   43       socklen_t len = sizeof(sockaddr_in);
   44       int ret=bind(_sockfd,(struct sockaddr*)&addr,len);
   45 
   46       if (ret < 0) {
   47         perror("bind error");
   48         return false;
   49       }
   50       return true;
   51   
   52     }
   53     
   54 
   55     //监听接口(操作句柄,最大连接数)
   56     //封装接口(最大连接数)
W> 57     bool Listen(int big =LISTEN_MAX){
   58       int ret=listen(_sockfd,LISTEN_MAX);
   59       if(ret<0){
   60         return false;
   61       }
   62       return true;
   63     }
   64 
   65 
   66 
   67     //申请链接(操作句柄,服务端的地址,地址长度)
   68     //封装的接口(服务器ip,服务器port)
   69     bool Connect(const std::string &ip,uint16_t port){
   70       sockaddr_in addr;
   71       addr.sin_family = AF_INET;
   72       addr.sin_port = htons(port);
   73       addr.sin_addr.s_addr = inet_addr(&ip[0]);
   74       socklen_t len = sizeof(sockaddr_in);
   75       int ret = connect(_sockfd, (sockaddr*)&addr, len);
   76       if (ret < 0) {
   77         perror("connect error");
   78         return false;
   79     }
   80       return true;
   81     }
   82                                                                                                                                                                                                              
   83     //封装的接口:
   84     //注意三个都是输出型参数(因为是处理请求的接口,所以不需要输入什么信息,只需要获得)
   85     //服务器同意链接接口(新的套接字地址,客户端的地址结构指针,地址结构大小指针)
   86     //注意这个的前提是客户端发送请求了,所以后两个数输出结构,用来获取请求的客户端的地址信息,当然可以不获取
   87     
   88     //原始的的接口(监听套接字,获取客户端的ip,客户端的port地址)
   89     //返回值是一个新的套接字,专门用来服务客户端,所以监听套接字相当于门迎~
   90     //将返回值放到封装的接口参数1,原始接口2是输出参数,分开封装到封装接口的23
   91     bool Accept(TCPSocket *sock,std::string *ip=NULL,uint16_t *port=NULL){
   92       //建立一个结构体用来存客户端的地址信息
   93       sockaddr_in addr;
   94       socklen_t len = sizeof(sockaddr_in);
   95 
   96       //注意这个len是输出型参数,所以是取地址!!!!
   97       int newfd=accept(_sockfd,(sockaddr*)&addr,&len);
   98       if (newfd < 0) {
   99         perror("accept error");
  100         return false;
  101     }
  102 
  103       //将输出型参数1填复制的句柄
  104       sock->_sockfd = newfd;
  105       if (ip != NULL) {
  106            *ip = inet_ntoa(addr.sin_addr);             
  107       }                                                  
  108       if (port != NULL) {
  109            *port = ntohs(addr.sin_port);             
  110       }
  111          return true;
  112     
  113   }
  114 
  115   
  116     //接收数据(操作句柄,接收缓冲区,接收数据长度,0阻塞)
  117     //封装的接口(接收缓冲区)
  118     //注意返回值小于0错误
  119     //等于0连接断开
  120     //大于0,实际大小
  121     bool Recv(std::string *buf){
  122       char tmp[1024]={0};
  123       int ret=recv(_sockfd,tmp,1024,0);
  124       if (ret < 0) {                                                                                                                                                                                         
  125         perror("recv error");
  126         return false;
  127       }
  128       else if (ret == 0) {
  129 
  130         printf("peer shutdown");
  131         return false;
  132       }
  133       //把缓冲区的内容放入真正的缓冲区
  134       //因为原始接口需要指定一次接受的数据大小
  135       //而直接用缓冲区接收不能确定大小
  136       buf->assign(tmp, ret);
  137       return true;
  138     }
  139 
  140     //发送数据(描述符,数据,长度,标志位0阻塞)
  141     //封装的接口(要发送数据)
  142     //返回实际的长度
  143     //这里需要考虑一次发送不完全部数据该怎么办
  144     bool Send(const std::string &data){
  145     int total;
W>146     while(total < data.size()){
  147 
  148       int ret = send(_sockfd, &data[0] + total,data.size() - total, 0);
  149       if(ret<0){
  150 
  151         perror("send error");
  152         return false;
  153       }
  154       total+=ret;
  155     }
  156     return true;
  157     }
  158 
  159     bool Close(){
  160       if (_sockfd != -1) {
  161         close(_sockfd);
  162       }
  163     return true;
  164     }
  165 
  166 };                              

TCP单执行流服务器

  1 #include<iostream>
  2 #include<string>
  3 #include"classtcp.hpp"
  4 #define CHECK_RET(q) if((q)==false){return -1;}
  5 //我们用程序运行参数来直接绑定服务器的地址信息
  6 //argv[0]就是这个程序的运行 argv[1]ip地址 argv[2]port号
  7 int main(int argc,char *argv[]){
  8   if (argc != 3) {
  9     printf("error\n");
 10     return -1;
 11   }
 12   //0.取出服务器的ip和port
 13   std::string sip=argv[1];
 14   //stoi把字符串转int
 15   uint16_t srvport = atoi(argv[2]);                                                                                                                                                                            
 16   //1.创建套接字
 17   TCPSocket listen_sockfd;
 18   CHECK_RET(listen_sockfd.Socket());
 19   //2.绑定地址信息
 20   CHECK_RET(listen_sockfd.Bind(sip,srvport));
 21   //3.开始监听
 22   CHECK_RET(listen_sockfd.Listen());
 23   while(1){
 24     //4.新建连接
 25     //注意要放到循环里,否则只会接受一次请求
 26     //开始写接受连接的三个输入参数
 27     TCPSocket clisock;
 28     std::string cliip;
 29     uint16_t cliport;
 30     bool ret = listen_sockfd.Accept(&clisock, &cliip,&cliport);
 31     //注意要失败写continue,因为服务端可不能关闭
 32     if (ret == false) {
 33       continue;
 34     }
 35     //打印客户端的地址演示
 36     std::cout<<"get newconn:"<< cliip<<"-"<<cliport<<"\n";
 37 
 38 
 39     //5.收发数据
 40     std::string buf;
 41     ret = clisock.Recv(&buf);
 42     if (ret == false) {
 43       //注意走到这里套接字已经产生,注意销毁!!
 44       clisock.Close();
 45       continue;
 46     }
 47     std::cout << "client say: " << buf << std::endl;
 48 
 49     buf.clear();//清空缓冲区
 50     std::cout << "server say: ";
 51     std::cin >> buf;
 52     ret = clisock.Send(buf);
 53     if (ret == false) {
 54       clisock.Close();
 55     }
 56 }
 57     //6.关闭监听套接字
 58     listen_sockfd.Close();
 59     
 60   
 61 }       

TCP客户端

  1 #include "classtcp.hpp"
  2 
  3 int main(int argc, char *argv[])
  4 {
  5     //通过参数传入要连接的服务器服务端的地址信息
  6     if (argc != 3) {
  7         printf("error\n");
  8         return -1;
  9     }
 10     //存放需要访问的服务器的地址信息
 11     std::string srvip = argv[1];
 12     //字符串转数字
 13     uint16_t srvport = std::stoi(argv[2]);                                                                                                                                                                     
 14 
 15     //建立监听套接字
 16     TCPSocket cli_sock;
 17     //1. 创建套接字
 18     CHECK_RET(cli_sock.Socket());
 19     //2. 绑定地址信息(不推荐)
 20     //3. 向服务端发起连接
 21     CHECK_RET(cli_sock.Connect(srvip, srvport));
 22     while(1) {
 23         //4. 收发数据
 24         std::string buf;
 25         std::cout << "client say: ";
 26         std::cin >> buf;
 27         CHECK_RET(cli_sock.Send(buf));
 28 
 29         buf.clear();
 30         CHECK_RET(cli_sock.Recv(&buf));
 31         std::cout << "server say: " << buf << std::endl;
 32     }
 33     //5. 关闭套接字
 34     CHECK_RET(cli_sock.Close());
 35     return 0;
 36 }

TCP多执行流(线程)

  1 #include"classtcp.hpp"                                                                                                                                                                                      
  2 #include<pthread.h>
  3 #define CHECK_RET(q) if((q)==false){return -1;}
  4                                                
  5 //多线程版本就是把数据的收发分配给线程独立处理 
  6 //在线程的入口函数中,需要把处理客户端请求套接字转成指针发送过来,并且再转换成tcp套接字类型用指针处理函数。
  7 //注意相比于普通版本,在入口函数退出时不能使用continue,需要把动态申请的空间释放,并退出(因为是单线程处理任务请求)
  8 void *thr_entry(void *arg){                                                                                         
  9   bool ret;                                                                                                         
 10   TCPSocket* clisock=(TCPSocket*)arg;
 11   while(1){                          
 12                                      
 13     std::string buf;
 14     //5.收发数据    
 15     ret = clisock->Recv(&buf);
 16     if (ret == false) {       
 17       //注意走到这里套接字已经产生,注意销毁!!
 18       clisock->Close();                         
 19       delete clisock;                           
 20       return NULL;     
 21       //continue;    
 22     }             
 23     std::cout << "client say: " << buf << std::endl;
 24                                                     
 25     buf.clear();//清空缓冲区                        
 26     std::cout << "server say: ";
 27     std::cin >> buf;            
 28     ret = clisock->Send(buf);   
 29     if (ret == false) {      
 30       clisock->Close();      
 31       delete clisock;  
 32       return NULL;     
 33     }                
 34                   
 35   }  
 36   clisock->Close();
 37   delete clisock;  
 38   return NULL;     
 39 }                
 40 
 41 //我们用程序运行参数来直接绑定
 42 //argv[0]就是这个程序的运行 argv[1]ip地址 argv[2]port号
 43 int main(int argc,char *argv[]){
 44   if (argc != 3) {
 45     printf("error\n");
 46     return -1;
 47   }
 48   //0.取出服务器的ip和port
 49   std::string sip=argv[1];
 50   //stoi把字符串转int
 51   uint16_t srvport = atoi(argv[2]);
 52   //1.创建套接字
 53   TCPSocket listen_sockfd;
 54   CHECK_RET(listen_sockfd.Socket());
 55   //2.绑定地址信息
 56   CHECK_RET(listen_sockfd.Bind(sip,srvport));
 57   //3.开始监听
 58   CHECK_RET(listen_sockfd.Listen());
 59   while(1){
 60     //4.新建连接
 61     //注意要放到循环里,否则只会接受一次请求
 62     //开始写接受连接的三个输入参数
 63 
 64     //这里不一样(线程)
 65     TCPSocket *clisock = new TCPSocket();
 66     //TCPSocket clisock;
 67     std::string cliip;
 68     uint16_t cliport;
 69     //没有新建连接就阻塞在这里
 70     //变成指针这里要去掉取地址,因为我们要把这个套接字的指针传到函数里
 71     bool ret = listen_sockfd.Accept(clisock, &cliip,&cliport);
 72     //bool ret = listen_sockfd.Accept(&clisock, &cliip,&cliport);
 73     //注意要失败写continue,因为服务端可不能关闭                                                                                                                                                            
 74     if (ret == false) {
 75       continue;
 76     }
 77     //打印客户端的地址演示
 78     std::cout<<"get newconn:"<< cliip<<"-"<<cliport<<"\n";
 79     
 80     //此时已经创建了新套接字,开始用线程交互
 81     //从这里开始进入线程
 82     pthread_t tid;
 83     //pthread_create(&tid, NULL, thr_entry, &clisock);
 84     //这里传递的是地址,随着while1的结束,栈变量会被释放
 85     //所以我们要new一个新的套接字,堆空间不会被释放,因为需要手动释放
 86     pthread_create(&tid, NULL, thr_entry, (void*)clisock);
 87     pthread_detach(tid);
 88     //clisock.Close();
 89     //注意这个不能随意释放,因为全部线程共享,某一个关闭就都不能用了
 90     //当某一个线程从入口函数出来不能释放!!
 91     
 92 
 93   }//6要写到while1外面!!!!
 94   //6.关闭监听套接字
 95   listen_sockfd.Close();
 96 
 97 
 98 }                                     

TCP多执行流(进程)

    1 #include"classtcp.hpp"
    2 #include <signal.h>
    3 #include <sys/wait.h>
    4 
W>  5 void sigcb(int no){
    6   while(waitpid(-1,NULL,WNOHANG)>0);                                                                                                                                                                                                
    7                                     
    8 }                                   
    9                                     
   10 void worker(TCPSocket &clisock){
   11   bool ret;                     
   12                                 
   13   while(1){                     
   14                  
   15     //5.收发数据 
   16     std::string buf;
   17     ret = clisock.Recv(&buf);
   18     if (ret == false) {      
   19       //注意走到这里套接字已经产生,注意销毁!!
   20       clisock.Close();                          
   21       exit(0);                                  
   22       //continue;                               
   23     }                 
   24     std::cout << "client say: " << buf << std::endl;
   25                                                     
   26     buf.clear();//清空缓冲区                        
   27     std::cout << "server say: ";                    
   28     std::cin >> buf;            
   29     ret = clisock.Send(buf);    
   30     if (ret == false) {         
   31       clisock.Close();//释放子进程里的clisock
   32       exit(0);                               
   33     }                                        
   34   }                                          
   35   clisock.Close();//释放子进程里的clisock
   36   exit(0);                               
   37 }                                        
   38                                          
   39 //我们用程序运行参数来直接绑定服务器的地址信息
   40 //argv[0]就是这个程序的运行 argv[1]ip地址 argv[2]port号
   41 int main(int argc,char *argv[]){                       
   42   if (argc != 3) {                                     
   43     printf("error\n");                                 
   44     return -1;                  
   45   }                   
   46                       
   47   //法1
   48   //避免产生
   49   //signal(SIGCHLD,SIG_IGN);
   50   //忽略子进程退出的信号
   51 
   52   //法2
   53   //写一个毁掉函数
   54   signal(SIGCHLD,sigcb);
   55 
   56 
   57 
   58   //0.取出服务器的ip和port
   59   std::string sip=argv[1];
   60   //stoi把字符串转int
   61   uint16_t srvport = atoi(argv[2]);
   62   //1.创建套接字
   63   TCPSocket listen_sockfd;
   64   CHECK_RET(listen_sockfd.Socket());
   65   //2.绑定地址信息
   66   CHECK_RET(listen_sockfd.Bind(sip,srvport));
   67   //3.开始监听
   68   CHECK_RET(listen_sockfd.Listen());
   69   while(1){
   70     //4.新建连接
   71     //注意要放到循环里,否则只会接受一次请求
   72     //开始写接受连接的三个输入参数
   73     TCPSocket clisock;
   74     std::string cliip;
   75     uint16_t cliport;
   76     bool ret = listen_sockfd.Accept(&clisock, &cliip,&cliport);
   77     //注意要失败写continue,因为服务端可不能关闭
   78     if (ret == false) {
   79       continue;
   80     }
   81     //打印客户端的地址演示
   82     std::cout<<"get newconn:"<< cliip<<"-"<<cliport<<"\n";
   83 
   84     //创建进程
   85     pid_t pid=fork();
   86     if(pid<0){                                                                                                                                                                                                                      
   87       clisock.Close();
   88       continue;//主进程继续去处理其他业务
   89     }else if(pid==0){
   90       //子进程
   91       worker(clisock);
   92     }
   93     //释放父进程里的clisock,因为子复制父进程,子进程的clisock去工作了而父进程的没工作,每次循环都创建了。
   94     clisock.Close();
   95   }  
   96   //  wait(NULL);//进程等待,但父进程不能卡在这里
   97 
   98   //6.关闭监听套接字
   99   listen_sockfd.Close();
  100 
  101 
  102 }                    
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值