tcp套接字编程

1.tcp协议:传输控制协议---面向连接,传输可靠,面向字节流。

实现数据可靠传输,传输灵活但是会造成数据粘包问题。

2.tcp客户端与服务端通信流程

 

3.代码实现

//客户端

  1 #include"tcpsocket.hpp"
  2 #include<signal.h>
  3 
  4 void sigcb(int signo)
  5 {
  6     printf("connection closed\n");
  7 } 
  8         
  9 int main(int argc, char* argv[])
 10 {       
 11     if(argc != 3)
 12     {  
 14         return -1;
 15     } 
 16     std::string ip = argv[1];
 17     uint16_t port = atoi(argv[2]); 
 18 
 19     signal(SIGPIPE, sigcb);
 20     TcpSocket sock; 
 21     CHECK_RET(sock.Socket());  
 22     CHECK_RET(sock.Connect(ip, port));
 23     while(1)
 24     { 
 25         std::string buf;
 26         std::cout<<"client say: ";
 27         fflush(stdout);
 28         std::cin>>buf;
 29         sock.Send(buf);
 30         buf.clear();
 31         sock.Recv(buf);
 32         std::cout<<"server say: "<<buf<<std::endl;
 33     }
 34     sock.Close();
 35     return 0;
 36 }
//服务端

  1 #include"tcpsocket.hpp"
  2 int main(int argc, char* argv[])
  3 {
  4     if(argc != 3)
  5     {
  7         return -1;
  8     }
  9     std::string ip = argv[1];
 10     uint16_t port = atoi(argv[2]);
 11     TcpSocket sock;
 12 
 13     CHECK_RET(sock.Socket());
 14     CHECK_RET(sock.Bind(ip, port));
 15     CHECK_RET(sock.Listen());
 16     while(1)
 17     {
 18         TcpSocket clisock;
 19         std::string cliip;
 20         uint16_t cliport;
 21         if(sock.Accept(clisock, cliip, cliport) == false)
 22         {
 23             continue;
 24         }
 25 
 26         std::cout<<"new client: "<<cliip<<":"<<cliport<<std::endl;
 27         while(1)
 28         {
 29         std::string buf;
 30         clisock.Recv(buf);
 31         std::cout<<"client say: "<<buf<<std::endl;
 32         buf.clear();
 33         std::cout<<"server say: ";
 34         fflush(stdout);
 35         std::cin>>buf;
 36         clisock.Send(buf);
 37         }
 38     }
 39     sock.Close();
 40     return 0;
 41 }

 

//封装好各个函数的头文件

  1 #include<iostream>
  2 #include<stdio.h>
  3 #include<string>
  4 #include<unistd.h>
  5 #include<stdlib.h>
  6 #include<errno.h>
  7 #include<netinet/in.h>
  8 #include<arpa/inet.h>
  9 #include<sys/socket.h>
 10 #define CHECK_RET(q) if((q) == false) { return -1;}
 11 
 12 class TcpSocket
 13 {
 14     public:
 15         TcpSocket(){}
 16         ~TcpSocket(){}
 17         bool Socket()
 18         {
 19             _socketfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
 20             if(_socketfd < 0)
 21             {
 22                 perror("socket error");
 23                 return false;
 24             }
 25             return true;
 26         }
 27 
 28         bool Bind(std::string& ip, uint16_t port)
 29         {
 30             struct sockaddr_in addr;
 31             addr.sin_family = AF_INET;
 32             addr.sin_port = htons(port);
 33             addr.sin_addr.s_addr = inet_addr(ip.c_str());
 34 
 35             socklen_t len = sizeof(struct sockaddr_in);
 36             int ret = bind(_socketfd, (struct sockaddr*)&addr, len);
 37             if(ret < 0)
 38             {
 39                 perror("bind error");
 40                 return false;
 41             }
 42             return true;
 43         }
 44 
 45         bool Listen(int backlog = 5)
 46         {
 47             int ret = listen(_socketfd, backlog);
 48             if(ret < 0)
 49             {
 50                 perror("listen error");
 51                 return false;
 52             }
 53             return true;
 54         }
 55 
 56         bool Accept(TcpSocket &cli, std::string &cliip, uint16_t port)
 57         {
 58             struct sockaddr_in addr;
 59             socklen_t len = sizeof(struct sockaddr_in);
 60             int sockfd = accept(_socketfd, (struct sockaddr*)&addr, &len);
 61             if(sockfd < 0)
 62             {
 63                 perror("accept error");
 64                 return false;
 65             }
 66             cli.SetFd(sockfd);
 67             cliip = inet_ntoa(addr.sin_addr);
 68             port = ntohs(addr.sin_port);
 69             return true;
 70         }
 71 
 72         void SetFd(int sockfd)
 73         {
 74             _socketfd = sockfd;
 75         }
 76 
 77         bool Connect(std::string& srv_ip, uint16_t srv_port)
 78         {
 79             struct sockaddr_in addr;
 80             addr.sin_family = AF_INET;
 81             addr.sin_port = htons(srv_port);
 82             addr.sin_addr.s_addr = inet_addr(srv_ip.c_str());
 83             socklen_t len = sizeof(struct sockaddr_in);
 84 
 85             int ret = connect(_socketfd, (struct sockaddr*)&addr, len);
 86             if(ret < 0)
 87             {
 88                 perror("connect error");
 89                 return false;
 90             }
 91             return true;
 92         }
 93 
 94         bool Send(std::string &buf)
 95         {
 96             int ret = send(_socketfd, buf.c_str(), buf.size(), 0);
 97             if(ret < 0)
 98             {
 99                 perror("send error");
100                 return false;
101             }
102             return true;
103         }
104 
105         bool Recv(std::string &buf)
106         {
107             char temp[1024];
108             int ret = recv(_socketfd, temp, 1024, 0);
109             if(ret < 0)
110             {
111                 perror("recv error");
112                 return false;
113             }
114             buf.assign(temp, ret);
115             return true;
116         }
117 
118         bool Close()
119         {
120             close(_socketfd);
121             return true;
122         }
123     private:
124         int _socketfd;
125 
126 };

4.代码中的几个函数:

htons():表示short型数据从主机字节序转到网络字节序。

h:host(主机);    n:net(网络);    s:short(短整型)/ l:long(长整型)

同理还有ntohs() 、htonl() 、htonl()。

inet_addr():将字符串点分十进制转化成网络字节序IP地址。

inet_ntos():将网络字节序转换成点分十进制形式IP地址。

listen():声明socket处于监听状态,其第二个参数为允许的客户端最大并发连接数。

accept():从已完成连接队列中获取一个客户端新建的已完成连接的socket,如果没有,则阻塞等待。

bind():客户端没有调用bind()函数,因为如果在同一个机器上启动多个客户端,就会出现端口号被占用导致不能正确建立连接。

recv():返回0时,一定是tcp断开连接,而不是传输数据大小为0,recv返回0,send触发异常。

 

5.使用上面的代码再启动一个客户端,尝试连接服务器,会发现第二个客户端不能正确与服务端通信,这是因为我们accept了一个请求之后,就一直在while循环尝试读取数据,没有继续调用accept导致不能接受新的请求。

所以使用多进程/多线程版本修改tcp的服务端程序即可。

//多进程版本服务端

#include <signal.h>
#include <sys/wait.h>
#include "tcpsocket.hpp"

void sigcb(int no){
    while(waitpid(-1, NULL, WNOHANG) > 0);
}
int main(int argc, char *argv[])
{
    if (argc != 3) {
        return -1;
    }
    std::string ip = argv[1];
    uint16_t port = atoi(argv[2]);

    signal(SIGCHLD, sigcb);

    TcpSocket sock;

    CHECK_RET(sock.Socket());
    CHECK_RET(sock.Bind(ip, port));
    CHECK_RET(sock.Listen());
    while(1) {
        TcpSocket clisock;
        std::string cliip;
        uint16_t cliport;
        if (sock.Accept(clisock, cliip, cliport) == false){
            continue;
        }
        std::cout<<"new client:"<<cliip<<":"<<cliport<<std::endl;

        int pid = fork();
        if (pid == 0) {
            while(1) {
                std::string buf;
                clisock.Recv(buf);
                std::cout<<"client say:"<<buf<<std::endl;

                buf.clear();
                std::cout<< "server say:";
                fflush(stdout);
                std::cin >> buf;
                clisock.Send(buf);
            }
            clisock.Close();
            exit(0);
        }
        //父进程一定要关闭这个套接字;因为父子进程数据独有
        //父进程关闭对子进程无影响;不关闭会造成资源泄漏
        clisock.Close();
    }
    sock.Close();
}
//多线程版本服务端

#include <pthread.h>
#include "tcpsocket.hpp"

void *thr_start(void *arg)
{
    TcpSocket *clisock = (TcpSocket*)arg;
    while(1) {
        std::string buf;
        clisock->Recv(buf);
        std::cout<<"client say:"<<buf<<std::endl;

        buf.clear();
        std::cout<< "server say:";
        fflush(stdout);
        std::cin >> buf;
        clisock->Send(buf);
    }
    clisock->Close();
    delete clisock;
    return NULL;
}

int main(int argc, char *argv[])
{
    if (argc != 3) {
        return -1;
    }
    std::string ip = argv[1];
    uint16_t port = atoi(argv[2]);

    TcpSocket sock;

    CHECK_RET(sock.Socket());
    CHECK_RET(sock.Bind(ip, port));
    CHECK_RET(sock.Listen());
    while(1) {
        TcpSocket *clisock = new TcpSocket();
        std::string cliip;
        uint16_t cliport;
        if (sock.Accept(*clisock, cliip, cliport) == false){
            continue;
        }
        std::cout<<"new client:"<<cliip<<":"<<cliport<<std::endl;

        pthread_t tid;
        pthread_create(&tid, NULL, thr_start, (void*)clisock);
        pthread_detach(tid);
    }
    sock.Close();
}

 在多线程版本中,由于在线程之间共享文件描述符,一个线程中打开的文件,另一个线程只要获取到这个文件的描述符,也可以对其进行操作。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值