TCP实现简单聊天程序

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Hanani_Jia/article/details/83449090

  上次我们通过UDP来实现了简单的聊天程序,这次我们用TCP协议来实现,TCP和UDP不同的是TCP需要创建连接。

//这是一个通过TCP协议来实现聊天的程序
//1.创建socket
//2.为socket绑定地址
//3.向服务端发送链接请求
//4.发送数据
//5.接受数据
//6.断开连接
//

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>

int main(int argc,char *argv[])
{
    if(argc!=3)
    {
        printf("please input:tcp ip port\n");
        return -1;
    }
    int sockfd=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    if(sockfd<0)
    {
        perror("socket error\n");
        return -1;
    }
    //2.和UDP协议一样客户端不推荐绑定端口地址
    //3.向服务器端发送链接请求
    struct sockaddr_in cli_addr;
    cli_addr.sin_family=AF_INET;
    cli_addr.sin_port=htons(atoi(argv[2]));
    cli_addr.sin_addr.s_addr=inet_addr(argv[1]);
    //通过命令行参数来给结构体赋值
    socklen_t len=sizeof(struct sockaddr_in);
    int ret=connect(sockfd,(struct sockaddr*)&cli_addr,len);
    //connect 向服务端发送链接请求
    //第一个参数是sockfd也就是操作描述符
    //第二个参数为要连接的服务器地址
    //第三个参数为地址信息的长度
    //返回值如果成功为0,失败为-1
    if(ret<0)
    {
      perror("connect error\n");
      return -1;
    }
    while(1)
    {
        //发送数据
        char buff[1024]={0};
        scanf("%s",buff);
        send(sockfd,buff,strlen(buff),0);
        //send 函数用来给已经链接的socket发送数据
        //第一个参数为sockfd 操作符
        //第二个参数为发送的数据
        //第三个参数为发送的数据长度
        //第四个参数为执行方式0位阻塞
        //
        //接受数据
        memset(buff,0x00,1024);
        ssize_t rlen=recv(sockfd,buff,1023,0);
        //recv  函数用来接受已经链接的socket接受数据
        //第一个参数是sockfd操作符
        //第二个参数为接受数据的缓冲区
        //第三个参数为接受数据的长度
        //第四个参数0为阻塞接受
        if(rlen<=0)
        {
            perror("recv error\n");
            return -1;
        }
        printf("server say:%s",buff);


    }
    close(sockfd);
    return 0;

}

这是聊天程序的客户端,对于客户端和UDP很相似不过需要向服务端发送connect连接,只有连接上了之后两个程序才能发送数据。

//这是TCP协议来实现服务端的程序
//1.创建socket
//2.为socket绑定地址
//3.开始监听,可以接受客户端的链接请求
//4.获取连接成功的socket
//5.接受数据
//6.发送数据
//7.关闭socket
//
//
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>

int main(int argc,char *argv[])
{
   if(argc!=3)
   {
       printf("please input:tcp ip port\n");
       return -1;
   }
   int sockfd=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
   if(sockfd<0)
   { 
      perror("socker error\n");
      return -1;
   }
   struct sockaddr_in ser_addr;
   ser_addr.sin_family=AF_INET;
   ser_addr.sin_port=htons(atoi(argv[2]));
   ser_addr.sin_addr.s_addr=inet_addr(argv[1]);
   socklen_t len=sizeof(struct sockaddr_in);
   int ret=bind(sockfd,(struct sockaddr*)&ser_addr,len);
   //为socket绑定地址
   if(ret<0)
   {
   perror("bind error\n");
   return -1;
   }
   //服务端开始监听 这时候服务端可以请求链接
   if(listen(sockfd,5)<0)
   {
      //listen 函数服务端用来监听
      //第一个参数为socket描述符
      //第二个参数为 最大的同时并发连接数
      perror("listen error\n");
      return -1;
   }
   while(1)
   {
      int new_sockfd; 
      struct sockaddr_in addr;
      len=sizeof(struct sockaddr_in);
      new_sockfd=accept(sockfd,(struct sockaddr*)&addr,&len);
      //获取连接成功的socket int accept
      //第一个参数为socket描述符
      //第二个参数为新建立链接的客户端地址信息
      //第三个参数为地址信息的长度
      //返回值如果成功返回新的描述符,如果失败返回-1
      //accept是一个阻塞函数链接成功队列中如果没有新的链接
      //那就会一直阻塞直到有新的客户端链接到来
      //这里我们通过创新新的socket来接受数据
      //客户端发送的数据都在这个新的socket缓冲区中
      //以前的socket被用来处理链接,链接成功的socket
      //发送数据都在新的socket的缓冲区
      if(new_sockfd<0)
      {
          perror("accept error\n");
          continue;//这里不能直接退出,因为这个链接失败了可以链接别的socket
      }
      printf("new socket:%s,%d,\n",inet_ntoa(addr.sin_addr),ntohs(addr.sin_port));
      //这里我们输出一下链接过来的socket的ip地址和port端口号
      while(1)
      {
          //接受数据
          char buff[1024]={0};
          ssize_t rlen=recv(new_sockfd,buff,1023,0);
          //recv 接受数据 
          //第一个为sockfd描述符
          //第二个参数为接受数据的缓冲区
          //第三个为接受数据的长度
          //第四个参数为0是代表阻塞式接受
          //对于返回值有三种情况 如果错误返回-1,链接关闭返回0,正确的话返回的是接受的长度
          if(rlen<0)
          {
              perror("recv error\n");
              close(new_sockfd);
              continue;//这里和客户端不同不能直接关闭你的程序
          }
          else if(rlen==0)
          {
              printf("perr shutdown\n");
              close(new_sockfd);
              continue;//如果返回0的话代表那个链接断开了
          }
          printf("client %s:%d say:%s\n",inet_ntoa(addr.sin_addr),ntohs(addr.sin_port),buff);
          //发送数据
          memset(buff,0x00,1024);
          scanf("%s",buff);
          send(new_sockfd,buff,strlen(buff),0);
      }
   }

     close(sockfd);
     return 0;
}

对于服务端就需要不断的来监听连接,也就是你需要随时准备着有人来向你发起链接,链接成功后接受到了客户端的socket就可以来发送和接受数据。

 

我们用的是9999端口,这里可以看到如果我们先运行客户端是不能运行的,因为客户端会向服务器发送链接请求,如果没有服务器的话就会请求失败,程序就不能继续往下进行,因为TCP是必须建立起来链接才可以发送和接受数据,并且这个程序也比较简单,如果请求失败的话就会直接退出。

当我们的服务端开始运行之后,再打开我们的客户端,还没有发送数据前,就可以打印出来socket的信息,也就是客户端的信息,不过这里有个小小的问题,我们客户端并没有绑定端口,所以这里打印的端口并不是我们在命令行输入的端口信息。

客户端也可以正常接收信息,不过这里和UDP一样,我们写的程序比较简单,并且接受和发送函数都是阻塞的,所以必须一个说完另一个说才可以。用户体验就不是很好。

这是我们的服务端,如果客户端先退出之后,服务端就会提示错误。

猜你喜欢

转载自blog.csdn.net/Hanani_Jia/article/details/83449090