socket编程-TCP


title: socket编程–TCP
date: 2019-07-19 14:48:20
tags: Linux网络

TCP网络编程

TCP网络编程流程

在这里插入图片描述

socket的API接口:
// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器) 
  int socket(int domain, int type, int protocol);
 参数:
 domain:地址域
 AF_INET:IPV4网路协议地址域
 type:套接字类型
 SOCK_STREAM  流式套接字,默认协议TCP不支持UDP
 SOCK_DGRAM   数据报套接字 默认UDP,不支持TCP
 protocol:协议类型
 0:使用套接字默认协议
 6/IPPROTO_TCP tcp协议
 17/IPPROTO_TCP UDP协议
 返回值:
 套接字操作句柄文件描述符
 失败-1;
 
 
// 绑定端口号 (TCP/UDP, 服务器)      
 int bind(int socket, const struct sockaddr *address,  
                                   socklen_t address_len);
  参数:
  sockfd:   创建套接字返回的描述符
  addr:     地址信息
  addrlen    地址信息长度
  返回值      失败返回-1成功0
 //接受数据
  ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                        struct sockaddr *src_addr, socklen_t *addrlen);
参数:
 sockfd:   创建套接字返回的描述符
 buf:      接收的数据
 len:      想要接收数据的长度
 flags		0默认阻塞接收
 saddr:    发送端地址信息
 addrlen    地址信息长度(输入输出型参数)不但指定接收多长还要保存实际接受多长
 返回值:
 返回实际接受长度
 -1 失败
 //发送数据
 ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                      const struct sockaddr *dest_addr, socklen_t addrlen);
参数:
sockfd:   创建套接字返回的描述符
 buf:      发送的数据
 len:      想要发送数据的长度
 flags		0默认阻塞发送
 daddr:     目的端信息
 addrlen    地址信息长度
 返回值:
成功:实际发送的长度
失败: -1
Listen()
 int listen(int socket, int backlog);开始监听socket (TCP, 服务器) 
 参数:
  socket:套接字描述符
  被listen函数作用的套接字,sockfd之前由socket函数返回。在被socket函数返回的套接字fd之时,它是一个   主动连接的套接字,也就是此时系统假设用户会对这个套接字调用connect函数,期待它主动与其它进程连接,然后   在服务器编程中,用户希望这个套接字可以接受外来的连接请求,也就是被动等待用户来连接。由于系统默认时认为   一个套接字是主动连接的,所以需要通过某种方式来告诉系统,用户进程通过系统调用listen来完成这件事。
  backlog:最大监听数目
  如果将 backlog 的值设置为 SOMAXCONN,就由系统来决定请求队列长度,这个值一般比较大,可能是几百,或者更多。

Listen()函数在一个socket的句柄上监听连接。这个函数可以让sockfd参数引用的那个socket的句柄标记成一个**被动式的socket,**即所谓被动监听,是指当没有客户端请求时,套接字处于“睡眠”状态,只有当接收到客户端请求时,套接字才会被“唤醒”来响应请求。

当进程处理一个一个连接请求的时候,可能还存在其它的连接请求。因为TCP连接是一个过程,所以可能存在一种半连接的状态,有时由于同时尝试连接的用户过多,使得服务器进程无法快速地完成连接请求。如果这个情况出现了,服务器进程希望内核如何处理呢?内核会在自己的进程空间里维护一个队列(baklog)以跟踪这些完成的连接但服务器进程还没有接手处理(已完成连接队列)或正在进行的连接(未完成连接队列),这样的一个队列内核不可能让其任意大,所以必须有一个大小的上限。这个backlog告诉内核使用这个数值作为上限。

在这里插入图片描述

accpect()
// 接收请求 (TCP, 服务器) 
  int accept(int socket, struct sockaddr* address,          
                         socklen_t* address_len);
ac 
阻塞:
如果队列中没有等待的连接,套接字也没有被标记为Non-blocking,accept()会阻塞调用函数直到连接出现;如果套接字被标记为Non-blocking,队列中也没有等待的连接,accept()返回错误EAGAIN或EWOULDBLOCK。
connect()
// 建立连接 (TCP, 客户端) 
int connect(int sockfd, const struct sockaddr *addr,         
                                  socklen_t addrlen); 
参数sockfd
指定数据发送的套接字,解决从哪里发送的问题。内核需要维护大量IO通道,所以用户必需通过这个参数告诉内核从哪个IO通道,此处就是从哪个socket接口中发送数据。sockfd是先前socket返回的值。
参数server_addr
指定数据发送的目的地,也就是服务器端的地址。这里服务器是针对connect说的,因为connect是主动连接的一方调用的,所以相应的要存在一个被连接的一方,被动连接的一方需要调用listen以接受connect的连接请求,如此被动连接的一方就是服务器了。
参数addrlen 
指定server_addr结构体的长度。我们知道系统中存在大量的地址结构,但socket接口只是通过一个统一的结构来指定参数类型,所以需要指定一个长度,以使内核在进行参数复制的时候有个有个界限。
c++封装TCP接口
include <iostream>
#include <string.h>
#include <string>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>

class TcpSocket{
  public:
    TcpSocket():_sockfd(-1){}
    void SetSockfd(int newfd){
      _sockfd = newfd;
    }
    bool Socket(){
     _sockfd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
     if(_sockfd < 0){
       perror("socket error");
       return false;
     }
     return true;
    }
    bool Bind(std::string& ip,uint16_t port){
      struct sockaddr_in addr;
      addr.sin_family = AF_INET;
      addr.sin_port = htons(port);
      addr.sin_addr.s_addr = inet_addr(ip.c_str());

      socklen_t len = sizeof(sockaddr_in);
      int ret = bind(_sockfd,(struct sockaddr*)&addr,len);
      if(ret < 0){
        perror("bind error");
        return false;
      }
      return true;
    }
    bool Listen(int backlog = 10){
      int ret = listen(_sockfd,backlog);
      if(ret <0){
        perror("listen error");
        return false;
      }
      return true;
    }
    bool Connect(std::string& ip,uint16_t port){
      sockaddr_in addr;
     addr.sin_family = AF_INET;
     addr.sin_port = htons(port);
     addr.sin_addr.s_addr = inet_addr(ip.c_str());
     socklen_t len = sizeof(struct sockaddr_in);
      int ret = connect(_sockfd,(struct sockaddr*)&addr,len);
      if(ret < 0){
        perror("connect error");
        return false;
      }
      return true;
    }
    bool Recv(std::string& buf){
      char tmp[4096] = {0};
      ssize_t ret = recv(_sockfd,tmp,4096,0);
      if(ret < 0){
        perror("recv error");
        return false;
      }else if(ret == 0){
        std::cout<<"perr shutdown"<<std::endl;
        return false;
      }
      buf.assign(tmp,ret);
      return true;
    }
    bool Accept(TcpSocket& csock,struct sockaddr_in* addr = NULL){
           struct sockaddr_in _addr;
           socklen_t len = sizeof(addr);
           int newfd = accept(_sockfd,(struct sockaddr*)&_addr,&len);
           if(newfd < 0){
             perror("accept error");
             return false;
           }
           if(addr != NULL){
            memcpy(addr,&_addr,len);
           }
           csock.SetSockfd(newfd);
           return true;
    }
    bool Send(std::string& buf){
      int ret = send(_sockfd,buf.c_str(),buf.size(),0);
      if(ret < 0){
        perror("send error");
      return false;
      }
      return true;
    }
    void Close(){
      close(_sockfd);
    }
  private:
    int _sockfd;
};

TCPSEVER
#include "tcpsocket.hpp"
#define CHECK_RIGHT(q) if((q == false)) {return -1;}
int main(int argc,char* argv[]){
  if(argc < 3){
    std::cout<<"./tcp_srv ip port"<<std::endl;
    return -1;
  }
  std::string ip = argv[1];
  uint16_t port = atoi(argv[2]);

  TcpSocket sock;
  CHECK_RIGHT(sock.Socket());
  CHECK_RIGHT(sock.Bind(ip,port));
  CHECK_RIGHT(sock.Listen());
  while(1){
    TcpSocket clisock;
    sockaddr_in addr;
    if(sock.Accept(clisock,&addr) == false){
      continue;
    }
    std::string buf;
    clisock.Recv(buf);
std::cout<<"client say:"<<buf<<std::endl;

buf.clear();
std::cout<<"sever say"<<std::endl;
fflush(stdout);
std::cin>>buf;
  }
  sock.Close();
  return 0;
}

TCPClient
#include "tcpsocket.hpp"
#define CHECK_RIGHT(q) if((q == false)) {return -1;}
int main(int argc,char* argv[]){
  if(argc < 3){
    std::cout<<"./tcp_cli ip port"<<std::endl;
    return -1;
  }
  std::string ip = argv[1];
  uint16_t port = atoi(argv[2]);

  TcpSocket sock;
  CHECK_RIGHT(sock.Socket());
  CHECK_RIGHT(sock.Connect(ip,port));
  while(1){
    std::string buf;
    std::cout<<"client say"<<std::endl;
    fflush(stdout);
    std::cin>>buf;
    sock.Send(buf);

    buf.clear();
    sock.Recv(buf);
    std::cout<<"server say:"<<buf<<std::endl;
  }
  sock.Close();
  return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值