LINUX下C++ Socket 网络通信简单实现

一 理论知识

1.1 概念以及socket种类

socket也叫“套接字”,虽然但是吧这个翻译其实很不好,不利于初学者理解,socket可以理解成计算机提供给程序员的接口,数据在客户端和服务端之间的socket之间传输。socket把复杂的TCP/IP协议封装,对于程序员来说只要利用好函数,就可以实现数据通信。
TCP提供了stream和datagram两种通信机制,所以socket分这两种。
stream的类型是SOCK_STREAM,采用TCP协议,TCP协议在计算机网络中是安全可靠的有连接的协议。datagram的类型是SOCK_DGRAM,采用的是UDP协议,UDP是不可靠的协议,现在在实际应用开发中,主要采用的是TCP。

二 server 端通信过程

  1. server端将一个套接字bind到指定的ip地址和port,并且通过该套接字等待和监听客户的连接请求
  2. 客户程序向服务端程序绑定的地址和端口发送连接请求
  3. 服务端接收请求
  4. server和client通过socket进行通信
    服务端工作流程
    LINUX下C++ Socket 网络通信简单实现_第1张图片
    代码编写如下,一步一步详细解答。
    头文件如下
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include 

第一步 为服务端创建socket

 int listenfd;
    listenfd=socket(AF_INET,SOCK_STREAM,0);//在socket编程中,AF_INET是必须的,等同于固定搭配
    //socket创建成功后如果返回值是-1的话,说明创建失败,为0的时候就是创建成功
    if(listenfd==-1)
    {
        printf("socket create fail\n");
        return -1;
    }

第二步 将socket绑定到提供的ip的地址和对应的端口上

    struct sockaddr_in serveraddr;//定义一个用来处理网络通信的数据结构,sockaddr_in分别将端口和地址存储在两个结构体中
    //sin_family协议族
    memset(&serveraddr,0,sizeof(serveraddr));
    serveraddr.sin_family=AF_INET;
    serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
    //serveraddr.sin_addr.s_addr=atoi(argv[1]);// specify ip address
    serveraddr.sin_port=htons(atoi(argv[1]));//specify port
    //printf("%s %s\n",argv[1],argv[2]);
    if(bind(listenfd,(struct sockaddr *)&serveraddr,sizeof(serveraddr))!=0)
    {
        printf("bind failed \n");
        return -1;
    }

argv[1]是运行可执行文件时,后面提供的参数。

第三步 利用listen函数使用主动连接套接口变为被连接套接口

使得一个进程可以接受其它进程的请求,从而成为一个服务器进程。在TCP服务器编程中listen函数把进程变为一个服务器,并指定相应的套接字变为被动连接。

 if(listen(listenfd,5)!=0)
    {
        printf("Listen failed\n");
        close(listenfd);
        return -1;
    }

函数声明

int listen(int sockfd, int backlog);//返回0=成功 -1则失败

参数sockfd是已经被bind过的socket。socket函数返回的socket是一个主动连接的socket,在服务端的编程中,程序员希望这个socket可以接受外来的连接请求,也就是被动等待客户端来连接。由于系统默认时认为一个socket是主动连接的,所以需要通过某种方式来告诉系统,程序员通过调用listen函数来完成这件事。
参数backlog,这个参数涉及到一些网络的细节,比较麻烦,填5、10都行,一般不超过30。当调用listen之后,服务端的socket就可以调用accept来接受客户端的连接请求

第四步 接受客户端的请求

服务端接受客户端的请求

int clintfd;//socket for client
    int socklen=sizeof(struct sockaddr_in);
    struct sockaddr_in client_addr;
    clintfd=accept(listenfd,(struct sockaddr*)&client_addr,(socklen_t *)&socklen);
    if(clintfd==-1)
        printf("connect failed\n");
    else
        printf("client %s has connnected\n",inet_ntoa(client_addr.sin_addr));
其中accept函数如下

LINUX下C++ Socket 网络通信简单实现_第2张图片

第五步 同客户端连接,接受数据

		int iret;
        memset(buf,0,sizeof(buf));
        iret=recv(clintfd,buf,strlen(buf),0);
        if(iret<=0)
        {
            perror("send");
            break;
        }
        printf("receive %s\n",buf);

recv函数用于server端socket传送过来的数据,函数声明如下:

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

这时候这里的sockfd其实是clientfd,buf中存储接受的数据,如果client的socket没有发送数据,recv函数就会一直等待,如果发送了数据,函数返回接受到的字符数,如果socket关闭那么就会返回0

    char buffer[1024];
    while (1)
    {
    int iret;
    memset(buffer,0,sizeof(buffer));
    iret=recv(clintfd,buffer,sizeof(buffer),0);
    if (iret<=0) 
    {
       printf("iret=%d\n",iret); break;  
    }
    printf("receive :%s\n",buffer);
 
    strcpy(buffer,"ok");//reply cilent with "ok"
    if ( (iret=send(clintfd,buffer,strlen(buffer),0))<=0) 
    { 
        perror("send"); 
        break; 
    }
    printf("send :%s\n",buffer);
  }

send函数用于把数据通过socket发送给对端。不论是客户端还是服务端,应用程序都用send函数来向TCP连接的另一端发送数据。

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

返回值是0的时候,通信已经中断。

第六步 关闭连接

close(listenfd); 
close(clintfd);

代码附录 server端

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include 
using namespace std;
int main(int argc,char *argv[])
{
    // first step ->create socket for server
    int listenfd;
    listenfd=socket(AF_INET,SOCK_STREAM,0);// in socket code,it must be AF_INET(protocol) 
    if(listenfd==-1)
    {
        printf("socket create fail\n");
        return -1;
    }
    //second step bind ->server's ip&port for communication to socket created in fist step
    struct sockaddr_in serveraddr;
    memset(&serveraddr,0,sizeof(serveraddr));
    serveraddr.sin_family=AF_INET;
    serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
    //serveraddr.sin_addr.s_addr=atoi(argv[1]);// specify ip address
    serveraddr.sin_port=htons(atoi(argv[1]));//specify port
    //printf("%s %s\n",argv[1],argv[2]);
    if(bind(listenfd,(struct sockaddr *)&serveraddr,sizeof(serveraddr))!=0)
    {
        printf("bind failed \n");
        return -1;
    }
    //Third step ->Set socket to listening mode
    /*
    The listen function changes the active connection socket interface into the connected socket interface, 
    so that a process can accept the requests of other processes and become a server process. 
    In TCP server programming, the listen function changes the process into a server and specifies that the corresponding socket becomes a passive connection.
    */
    if(listen(listenfd,5)!=0)
    {
        printf("Listen failed\n");
        close(listenfd);
        return -1;
    }
    // 4th step -> receive client's request
    int clintfd;//socket for client
    int socklen=sizeof(struct sockaddr_in);
    struct sockaddr_in client_addr;
    clintfd=accept(listenfd,(struct sockaddr*)&client_addr,(socklen_t *)&socklen);
    if(clintfd==-1)
        printf("connect failed\n");
    else
        printf("client %s has connnected\n",inet_ntoa(client_addr.sin_addr));
   
    // 5th step ->connect with client,receive data and reply OK
    char buffer[1024];
    while (1)
    {
    int iret;
    memset(buffer,0,sizeof(buffer));
    iret=recv(clintfd,buffer,sizeof(buffer),0);
    if (iret<=0) 
    {
       printf("iret=%d\n",iret); break;  
    }
    printf("receive :%s\n",buffer);
 
    strcpy(buffer,"ok");//reply cilent with "ok"
    if ( (iret=send(clintfd,buffer,strlen(buffer),0))<=0) 
    { 
        perror("send"); 
        break; 
    }
    printf("send :%s\n",buffer);
  }
    // 6th close socket
    close(listenfd); close(clintfd);
}

client端通信过程

流程如下
LINUX下C++ Socket 网络通信简单实现_第3张图片

所需头文件如下

#include 
#include 
#include 
#include 
#include 
#include 

第一步 为客服端创建socket

int sockfd;
sockfd = socket(AF_INET,SOCK_STREAM,0))//同服务端操作相同

第二步 向server发起连接请求

//way 1
  struct hostent* h;
  if ( (h = gethostbyname(argv[1])) == 0 )   // 指定服务端的ip地址。
  {
   printf("gethostbyname failed.\n"); 
   close(sockfd);
   return -1;
   }
  struct sockaddr_in servaddr;
  memset(&servaddr,0,sizeof(servaddr));
  servaddr.sin_family = AF_INET;
  servaddr.sin_port = htons(atoi(argv[2])); // 指定服务端的通信端口。
  memcpy(&servaddr.sin_addr,h->h_addr,h->h_length);
  if (connect(sockfd, (struct sockaddr *)&servaddr,sizeof(servaddr)) != 0) 
   // 向服务端发起连接清求。
  {
   perror("connect"); 
   close(sockfd); 
   return -1;
  }
  // way 2
  struct sockaddr_in servaddr;
  memset(&servaddr,0,sizeof(servaddr));
  servaddr.sin_family = AF_INET;
  servaddr.sin_port = htons(atoi(argv[2])); // server's port
  servaddr.sin_addr.s_addr=inet_addr(argv[1]);//server's ip
  if (connect(sockfd, (struct sockaddr *)&servaddr,sizeof(servaddr)) != 0)  // send request to server for connection
  { 
      perror("connect"); 
      close(sockfd); 
      return -1; 
  }

其中用到的结构体和函数介绍如下

struct hostent//

	struct hostent
	{
		char *h_name;         //正式主机名
		char **h_aliases;     //主机别名
		int h_addrtype;       //主机IP地址类型:IPV4-AF_INET
		int h_length;		  //主机IP地址字节长度,对于IPv4是四字节,即32位
		char **h_addr_list;	  //主机的IP地址列表
	};
	
	#define h_addr h_addr_list[0]   //保存的是IP地址

这里面使用的gethostbyname函数,只能用在客户端上,主要作用就是把字符串的IP地址转换成结构体的ip地址。

第三步 向server发送数据

    while(1){
	char buffer[1024];
	int iret;
	int choice;
	printf("if you want to continue to chat please input 1,or input 2\n");
	scanf("%d\n",&choice);
	if(choice==2)
	break;
    memset(buffer,0,sizeof(buffer));
    printf("please input what you want to say\n");
    scanf("%s",buffer);
    if ( (iret=send(sockfd,buffer,strlen(buffer),0))<=0) // 向服务端发送请求报文。
    {
      perror("send");
      break; 
    }
    printf("send:%s\n",buffer);
 
    memset(buffer,0,sizeof(buffer));
    if ( (iret=recv(sockfd,buffer,sizeof(buffer),0))<=0) // 接收服务端的回应报文。
    {
       printf("iret=%d\n",iret); 
       break;
    }
    printf("receive:%s\n",buffer);
    
  }
  }

第四步 关闭连接释放资源

 close(sockfd);

代码附录 clinet端

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include
#include
using namespace std;
int main(int argc,char *argv[])
{
  
  // first step create socket 
  int sockfd;
  if ( (sockfd = socket(AF_INET,SOCK_STREAM,0))==-1) 
  { 
      perror("socket"); 
      return -1; 
  }
 
  // second step: send request to server for connection
  struct sockaddr_in servaddr;
  memset(&servaddr,0,sizeof(servaddr));
  servaddr.sin_family = AF_INET;
  servaddr.sin_port = htons(atoi(argv[2])); // server's port
  servaddr.sin_addr.s_addr=inet_addr(argv[1]);//server's ip
  if (connect(sockfd, (struct sockaddr *)&servaddr,sizeof(servaddr)) != 0)  // send request to server for connection
  { 
      perror("connect"); 
      close(sockfd); 
      return -1; 
  }


  char buffer[1024];
 
  // 3th step connect with server 
  while(1)
  {
    int iret;
    memset(buffer,0,sizeof(buffer));
    int choice;
    printf("if you want to continue to chat please input 1 or input else\n");
    scanf("%d",&choice);
    getchar();
    if(choice!=1)
    break;
    else
    {
        printf("please input what you want to say\n");
        cin.getline(buffer,1024);
    }
        
    
    if ( (iret=send(sockfd,buffer,strlen(buffer),0))<=0) // send data to server
    { 
        perror("send"); 
        break; 
    }
    printf("send: %s\n",buffer);
 
    memset(buffer,0,sizeof(buffer));
    if ( (iret=recv(sockfd,buffer,sizeof(buffer),0))<=0) // receive server's reply
    {
       printf("iret=%d\n",iret); 
       break;
    }
    printf("receive: %s\n",buffer);
  }
  close(sockfd);
}

三 总结

TCP服务器端依次调用socket()、 bind()、listen()之后,就会监听指定的socket地址了。TCP客户端依次调用socket()、connect()之后就想TCP服务器发送了一个连接请求。TCP服务器监听到这个请求之后,就会调用accept()函数取接收请求,这样连接就建立好了。之后就可以开始网络I/O操作了,即类同于普通文件的读写I/O操作。

对于服务端来说有两个socket,这里该如何理解呢?

首先在linux内核中,socket函数创建的都是主动套接字,但是服务端在经过listen()以后呢,会转换成被动socket,经过accept函数处理后又会变成已连接的socket,在成为已连接socket之前,还是同一个socket,但是这里面状态发生了改变,服务端经过accept之后的socket是新的socket,用于连接之后的读写操作。
监听socket,是服务器作为客户端连接请求的一个对端,只需创建一次能够让客户端请求到有这个端点就ok,所以监听socket(listen_socket_fd)存在于服务器的整个生命周期, 不需要每个连接都创建一个新的监听socket出来, 没必要呢。已连接socket(connect_socket_fd)是客户端与服务器之间已经建立起来了的连接的一个端点,服务器每次接受连接请求时都会创建一个新已连接socket,它的生命周期只是客户端请求服务端的时间范围内。

你可能感兴趣的:(网络,tcp/ip,udp)