网络套接字---TCP

本文介绍了TCP协议的特点,对比了TCP与UDP的区别。接着详细讲解了socket编程接口,包括socket函数、bind函数、listen函数、accept函数、connect函数、recv函数和send函数的使用。还展示了基于TCP的客户端和服务器端通信的完整流程,包括三次握手和四次挥手。最后,给出了一个简单的TCP服务器和客户端实现示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

简单认识TCP协议

在上文中网络套接字—UDP我们认识了UDP之后,在此基础上再来了解TCP。

  • IP和端口号的概念和作用这里不在赘述,不了解的朋友请参考上文。
  • 这里主要说一下TCP的特点,与UDP的对比。
    在这里插入图片描述

socket编程接口详解

socket函数和bind函数在上文网络套接字—UDP中有详解:这里我们只是简单介绍:

socket函数

在这里插入图片描述

bind函数

在这里插入图片描述

  • 服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服务器程序的地址和端口号后就可以向服务器发起连接;服务器需要调用bind绑定一个固定的网络地址和端口号;
  • bind()的作用时将参数sockfd和myaddr绑定在一起,使sockfd这个用于网络通讯的文件描述符监听myaddr所描述的地址和端口号

在程序中我们对myaddr参数是这样初始化的:
在这里插入图片描述

listen函数

在这里插入图片描述

accept函数

在这里插入图片描述
理解accept的返回值:
accept函数的返回值同样也是一个文件描述符,当有客户端向服务器端发起连接请求(connect),并且服务器端响应了客户端的请求(accept),成功后accept函数就会返回一个文件描述符,成功多少,返回多少。

connect函数

在这里插入图片描述

recv函数和send函数

  • recv():接收数据
    在这里插入图片描述
  • send():发送数据
    在这里插入图片描述

基于TCP的客户端和服务器端通信的流程图

在这里插入图片描述
服务器初始化:

  • 调用socket, 创建文件描述符;
  • 调用bind, 将当前的文件描述符和ip/port绑定在一起; 如果这个端口已经被其他进程占用了, 就会bind失败;
  • 调用listen, 声明当前这个文件描述符作为一个服务器的文件描述符, 为后面的accept做好准备;
  • 调用accecpt, 并阻塞, 等待客户端连接过来;

建立连接的过程:

  • 调用socket, 创建文件描述符;
  • 调用connect, 向服务器发起连接请求;
  • connect会发出SYN段并阻塞等待服务器应答; (第一次)
    服务器收到客户端的SYN, 会应答一个SYN-ACK段表示"同意建立连接"; (第二次)
    客户端收到SYN-ACK后会从connect()返回, 同时应答一个ACK段; (第三次)

这个建立连接的过程, 通常称为 三次握手;
在这里插入图片描述
数据传输过程:

  • 建立连接后,TCP协议提供全双工的通信服务; 所谓全双工的意思是, 在同一条连接中, 同一时刻, 通信双方可以同时写数据; 相对的概念叫做半双工, 同一条连接在同一时刻, 只能由一方来写数据;
  • 服务器从accept()返回后立刻调 用read(), 读socket就像读管道一样, 如果没有数据到达就阻塞等待;这时客户端调用write()发送请求给服务器, 服务器收到后从read()返回,对客户端的请求进行处理, 在此期间客户端调用read()阻塞等待服务器的应答;
  • 服务器调用write()将处理结果发回给客户端, 再次调用read()阻塞等待下一条请求;客户端收到后从read()返回, 发送下一条请求,如此循环下去;

断开连接的过程:

  • 如果客户端没有更多的请求了, 就调用close()关闭连接, 客户端会向服务器发送FIN段(第一次);
    此时服务器收到FIN后, 会回应一个ACK, 同时read会返回0 (第二次);
    read返回之后, 服务器就知道客户端关闭了连接, 也调用close关闭连接, 这个时候服务器会向客户端发送一个FIN; (第三次)
    客户端收到FIN, 再返回一个ACK给服务器; (第四次)

这个断开连接的过程, 通常称为 四次挥手
在这里插入图片描述

一个简单的TCP通信

// tcpServer.hpp
#ifndef __TCP_SERVER_H__
#define __TCP_SERVER_H__ 

#include<iostream>
#include<string>
#include<cstdlib>
#include<cstring>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#define BACKLOG 5

class tcpServer{
  private:
    int port;
    int lsock;//监听套接字
  public:
    tcpServer(int _port)
    :port(_port),lsock(-1)
    {}
    void initServer()
    {
      lsock=socket(AF_INET,SOCK_STREAM,0);
      if(lsock<0){
        std::cerr<<"socket error"<<std::endl;
        exit(2);
      }
      struct sockaddr_in local;
      local.sin_family=AF_INET;
      local.sin_addr.s_addr=htonl(INADDR_ANY);
      local.sin_port=htons(port);
      if(bind(lsock,(struct sockaddr*)&local,sizeof(local))<0){
        std::cerr<<"bbind error"<<std::endl;
        exit(3);
      }
      if(listen(lsock,BACKLOG)<0){
        std::cerr<<"listen error"<<std::endl;
        exit(4);
      }
    }
    void start()
    {
      struct sockaddr_in endpoint;
      while(true)
      {
       socklen_t len =sizeof(endpoint);
       int sock=accept(lsock,(struct sockaddr*)&endpoint,&len);
         if(sock<0)
         {
            std::cerr<<"accept error"<<std::endl;
            continue;
         }
         std::cout<<"get a new link..."<<std::endl;
         service(sock);
      }
    }
    void service(int sock)
    {
      char buffer[1024];
      while(true)
      {
        size_t s=recv(sock,buffer,sizeof(buffer)-1,0);
        if(s>0)
        {
          buffer[s]=0;
          std::cout<<"client# "<<buffer<<std::endl;
          send(sock,buffer,strlen(buffer),0);
        }
        else if(s==0)
        {
          std::cout<<"client quit...."<<std::endl;
          close(sock);
          break;
        }
        else{
          std::cerr<<"recv error"<<std::endl;
          break;
        }
      }
    }
    ~tcpServer()
    {
      close(lsock);
    }
};

#endif
// tcpServer.cc
#include"tcpServer.hpp"
static void Usage(std::string proc)
{
  std::cout<<"Usage: "<<std::endl;
  std::cout<<'\t'<<proc<<" port"<<std::endl;
}
int main(int argc,char*argv[])
{
  if(argc!=2)
  {
    Usage(argv[0]);
    exit(1);
  }
  tcpServer *ts=new tcpServer(atoi(argv[1]));//将端口号以命令行参数的形式给出
  ts->initServer();
  ts->start();

  delete ts;
  return 0;
}
//##########################################
//tcpClient.hpp
#ifndef __TCP_SERVER_H__
#define __TCP_SERVER_H__ 

#include<iostream>
#include<string>
#include<cstdlib>
#include<cstring>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>

class tcpClient{
  private:
    std::string ip;
    int port;
    int lsock;
  public:
    tcpClient(std::string _ip,int _port)
    :ip(_ip),port(_port)
    {}
    void initClient()
    {
      lsock=socket(AF_INET,SOCK_STREAM,0);
      if(lsock<0){
        std::cerr<<"socket error"<<std::endl;
        exit(2);
      }
      struct sockaddr_in peer;
      peer.sin_family=AF_INET;
      peer.sin_addr.s_addr=inet_addr(ip.c_str());
      peer.sin_port=htons(port);
      if(connect(lsock,(struct sockaddr*)&peer,sizeof(peer))<0){
        std::cerr<<"connect  error"<<std::endl;
        exit(3);
      }
      service (lsock);
    }
    void service(int sock)
    {
      std::string msg;
      while(true)
      {
        std::cout<<"Please Enter# ";
        std::cin>>msg;
        if(msg=="quit")
          break;
        send(sock,msg.c_str(),msg.size(),0);
        char echo[1024];
        ssize_t s=recv(sock,echo,sizeof(echo)-1,0);
        if(s>0)
        {
          echo[s]=0;
          std::cout<<"server# "<<echo<<std::endl;
        }
        
      }
    }
    ~tcpClient()
    {close(lsock);}
};

#endif
//tcpClient.cc
#include"tcpClient.hpp"
void Usage(std::string proc)
{
  std::cout<<"Usage: "<<proc <<" svr_ip svr_port"<<std::endl;
}

int main(int argc,char*argv[])
{
  if(argc!=3){
    Usage(argv[0]);
    exit(1);
  }
  tcpClient *tc=new tcpClient(argv[1],atoi(argv[2]));//IP和端口号都以命令行参数给出
  tc->initClient();

  delete tc;
  return 0;
}

Makefile:
在这里插入图片描述

结果展示:
在这里插入图片描述

创建套接字操作系统底层干了什么?

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值