1. 程式人生 > >TCP實現簡單聊天程式

TCP實現簡單聊天程式

  上次我們通過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一樣,我們寫的程式比較簡單,並且接受和傳送函式都是阻塞的,所以必須一個說完另一個說才可以。使用者體驗就不是很好。

這是我們的服務端,如果客戶端先退出之後,服務端就會提示錯誤。