Linux Socket 下實現的網絡聊天室


一.原理分析:

①   :socket編程介紹

本實驗主要通過socket編程來實現,Socket接口是TCP/IP網絡的API,Socket接口定義了許多函數或例程,可以用它們來開發TCP/IP網絡上的應用程序。Socket將復雜的TCP/IP協議族隱藏在Socket接口后面,對用戶來說,一組簡單的接口就是全部,讓Socket去組織數據,以符合指定的協議。常用的Socket類型有兩種:流式Socket (SOCK_STREAM)和數據報式Socket(SOCK_DGRAM)。流式是一種面向連接的Socket,針對於面向連接的TCP服務應用;數據報式Socket是一種無連接的Socket,對應於無連接的UDP服務應用。

顯然,本次實驗使用的是面向連接的流式socket。

在不同的操作系統中,提供的socket也不同:

不同操作系統中的Socket

Windows Socket (Winsock)

Linux Socket (BSD Socket)

 

套接字socket的含義:

1.套接字是一個通信終結點,它是Socket應用程序用來在網絡上發送或接收數據包的對象

2.套接字具有類型,與正在運行的進程相關聯,並且可以有名稱

3.套接字一般只與使用網際協議組的同一“通信域”中的其他套接字交換數據

 

 

②   :Client/server通信模型介紹

本實驗選用Client/server通信模型作為實驗的具體結構。

在客戶/服務器模式中我們將請求服務的一方稱為客戶(client),將提供某種服務的一方稱為服務器(server)

一個服務程序通常在一個眾所周知的地址監聽對服務的請求,也就是說服務進程一直處於休眠狀態,直到一個客戶對這個服務的地址提出了連接請求。在這個時刻,服務程序被“喚醒”並且為客戶提供服務—對客戶的請求作出適當的反應。

客戶機和服務器的運行過程如下:

 

 

 

在實驗一中,客戶機的請求就是:發送給服務器一段沒有處理過的源字符串,要求服務器進行處理

服務器的響應就是:發給客戶機處理過的字符串

 

③   :使用socket編程在客戶機和服務器之間建立連接和傳輸數據的流程

給出流程圖:

 

 

 

各個環節使用的代碼會在后面代碼分析時講解

 

④:linux下基於c++的多線程編程

多線程是多任務處理的一種特殊形式,多任務處理允許讓電腦同時運行兩個或兩個以上的程序。一般情況下,兩種類型的多任務處理:基於進程和基於線程。

·      基於進程的多任務處理是程序的並發執行。

·      基於線程的多任務處理是同一程序的片段的並發執行。

 

多線程程序包含可以同時運行的兩個或多個部分。這樣的程序中的每個部分稱為一個線程,每個線程定義了一個單獨的執行路徑。

創建線程

下面的程序,可以用它來創建一個 POSIX 線程:

    #include <pthread.h>

    pthread_create (thread, attr, start_routine, arg)

在這里,pthread_create 創建一個新的線程,並讓它可執行。下面是關於參數的說明:

 

創建線程成功時,函數返回 0,若返回值不為 0 則說明創建線程失敗。

終止線程

使用下面的程序,可以用它來終止一個 POSIX 線程:

    #include <pthread.h>

    pthread_exit (status)

在這里,pthread_exit 用於顯式地退出一個線程。通常情況下,pthread_exit() 函數是在線程完成工作后無需繼續存在時被調用。

如果 main() 是在它所創建的線程之前結束,並通過 pthread_exit() 退出,那么其他線程將繼續執行。否則,它們將在 main() 結束時自動被終止。

 

二.實驗流程

本次實驗,我們采用Linux系統作為實驗載體。所以不用額外使用檢查系統協議棧安裝情況。所以大致流程為:

1.    分別創建client.c文件和sever.c文件

2.    使用socket()函數創建服務器端通信套接字

3.    使用bind()函數將創建的套接字與服務器地址綁定

4.    服務器使用listen()函數使服務器套接字做好接收連接請求准備,客戶機使用connect函數發出向服務器建立連接的請求(調用前可以不用bind()端口號,由系統自動完成)

5.    使用accept()接收來自客戶端由connect()函數發出的連接請求

6.    根據連接請求建立連接后,使用send()函數發送數據,或者使用recv()函數接收數據

7.    編寫處理函數,使服務端處理來自客戶段的請求,並進行響應

8.    使用closet()函數關閉套接字(可以先用shutdown()函數先關閉讀寫通道)

9.    Gcc c文件形成可執行程序

10.  運行可執行程序查看實驗效果:

 

實現多線程打開服務器

 

 

 

 

實現信息交互

 

 

 

 

三:關鍵代碼分析:

Sever端建立連接后,開啟新線程操作:

 

int session_fd = accept(server_socket_fd, (struct sockaddr*)&client_addr, &client_addr_length);
if(session_fd < 0)
{
perror("Server Accept Failed:");
// break;
}
char client_addr_res[20];
//char *ptr=inet_ntop(AF_INET, &client_addr.sin_addr, client_addr_res, strlen(client_addr_res));
printf("Get Connected with Client:%s ,the port is :%d Opening a new Thread...\n",inet_ntoa(client_addr.sin_addr),client_addr.sin_port);
pthread_t thread_id;
if (pthread_create(&thread_id, NULL, (void *)(&Data_handle), (void *)(&session_fd)) == -1)
{
fprintf(stderr, "pthread_create error!\n");
break; //break while loop
}


}

 

在子線程中進行數據處理,即接受客戶端發來的消息,然后驗證是否是“hi”信息,並發送回應

 

static void Data_handle(void * fd)
{
int session_fd = *((int *)fd);
// recv函數通過描述字讀取數據到字節流,並存儲到地址字符串
char buffer[BUFFER_SIZE];
bzero(buffer, BUFFER_SIZE);
if (recv(session_fd, buffer, BUFFER_SIZE, 0) < 0)
{
perror("Server Recieve Data Failed:");
}

if(strcmp(buffer,"hi")==0)
{
bzero(buffer, BUFFER_SIZE);

strcpy(buffer, "hello");

if (send(session_fd, buffer, BUFFER_SIZE, 0) < 0)
{
printf("Send Failed./n");
}

}

else
{

bzero(buffer, BUFFER_SIZE);

strcpy(buffer, "i do not understand");

if (send(session_fd, buffer,BUFFER_SIZE , 0) < 0)
{
printf("Send Failed./n");
}

}

close(session_fd);
pthread_exit(NULL); //terminate calling thread!
}

后面附上客戶端和服務端的代碼

client.c

#include<netinet/in.h>  

#include<sys/types.h>  

#include<sys/socket.h>   

#include<stdio.h>  

#include<stdlib.h>   

#include<string.h> 



#define SERVER_PORT 8000   

#define BUFFER_SIZE 1024   



void find_file_name(const char *name,const char *path);

int main()

{

	struct sockaddr_in client_addr;

	bzero(&client_addr, sizeof(client_addr));

	client_addr.sin_family = AF_INET;

	client_addr.sin_addr.s_addr = htons(INADDR_ANY);

	client_addr.sin_port = htons(0);

  

	int client_socket_fd = socket(AF_INET, SOCK_STREAM, 0);

	if (client_socket_fd < 0)

	{

		perror("Create Socket Failed:");

		exit(1);

	}



	if (-1 == (bind(client_socket_fd, (struct sockaddr*)&client_addr, sizeof(client_addr))))

	{

		perror("Client Bind Failed:");

		exit(1);

	}

//////////////////////////////////////////////////////////////////////////////////////////////////////

	// 聲明一個服務器端的socket地址結構,並用服務器那邊的IP地址及端口對其進行初始化,用於后面的連接   

	struct sockaddr_in server_addr;

	bzero(&server_addr, sizeof(server_addr));

	server_addr.sin_family = AF_INET;

	//將點分十進制串轉換成網絡字節序二進制值,此函數對IPv4地址和IPv6地址都能處理。

	//	第一個參數可以是AF_INET或AF_INET6:

	//	第二個參數是一個指向點分十進制串的指針:

	//	第三個參數是一個指向轉換后的網絡字節序的二進制值的指針。

	if (inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr) == 0)

	{

		perror("Server IP Address Error:");

		exit(1);

	}



	server_addr.sin_port = htons(SERVER_PORT);

	socklen_t server_addr_length = sizeof(server_addr);



	// int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); 

	// sockfd:第一個參數即為客戶端的socket描述字

	//	addr:當前客戶端的本地地址,是一個 struct sockaddr_un 類型的變量, 在不同主機中是struct sockaddr_in 類型的變量,

	//	addrlen:表示本地地址的字節長度

	//	返回值 : 成功標志

	if (connect(client_socket_fd, (struct sockaddr*)&server_addr, server_addr_length) < 0)

	{

		perror("Can Not Connect To Server IP:");

		exit(0);

	}

   

        char buffer[BUFFER_SIZE];

	bzero(buffer, BUFFER_SIZE);

          

	printf("Input the hello on Sever:\t");

	scanf("%s", buffer);



	

	

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

	//socket:如果是服務端則是accpet()函數的返回值,客戶端是connect()函數中的第一個參數

	// buffer:寫入或者讀取的數據

	// len:寫入或者讀取的數據的大小

	if (send(client_socket_fd, buffer, BUFFER_SIZE, 0) < 0)

	{

		perror("Send Failed:");

		exit(1);

	}





	while (recv(client_socket_fd, buffer, BUFFER_SIZE, 0)> 0)

	{

		        

			printf("from server %s \n", buffer);

                        if(strcmp(buffer,"hello")==0)
                      { 
                        printf("get the hello from Sever:\n");
                        

			break;}

		

	}



	

	close(client_socket_fd);

	return 0;

}

 

server.c

#include<stdlib.h>

#include<pthread.h>

#include<netinet/in.h> 

#include<sys/types.h>   

#include<sys/socket.h>  

#include<stdio.h>    

#include<stdlib.h>  

#include<string.h>     

#include<arpa/inet.h>



#define SERVER_PORT 8000   

#define LENGTH_OF_LISTEN_QUEUE 20   

#define BUFFER_SIZE 1024   

#define FILE_NAME_MAX_SIZE 512   

static void Data_handle(void * sock_fd);

int main(void)   

{   



////////////////////////////////////////////////////////////////////////////////////////////////////////////

  // 聲明並初始化一個服務器端的socket地址結構,sockaddr_in是internet環境下套接字的地址形式

  //sockaddr_in(在netinet / in.h中定義):

  //    struct  sockaddr_in {

  //    short  int  sin_family;                      /* Address family */

  //    unsigned  short  int  sin_port;       /* Port number */

  //    struct  in_addr  sin_addr;              /* Internet address */

  //    unsigned  char  sin_zero[8];         /* Same size as struct sockaddr */

  //};

  //struct  in_addr {unsigned  long  s_addr;};

  struct sockaddr_in server_addr;   

  bzero(&server_addr, sizeof(server_addr)); 

  //Sa_family: 是地址家族,也成作,協議族,一般都是"AF_XXX"的形式,常用的有

  //AF_INET  Arpa(TCP / IP) 網絡通信協議

  //AF_UNIX  UNIX 域協議(文件系統套接字)

  //AF_ISO    ISO標准協議

  //AF_NS    施樂網絡體統協議

  //AF_IPX  Novell IPX 協議

  //AF_APPLETALK   Appletalk DDS

  server_addr.sin_family = AF_INET;   



  //htons是將整型變量從主機字節順序轉變成網絡字節順序, 就是整數在地址空間存儲方式變為高位字節存放在內存的低地址處。

  //INADDR_ANY:0.0.0.0,泛指本機的意思,也就是表示本機的所有IP,監聽本機所有網卡

  server_addr.sin_addr.s_addr = htons(INADDR_ANY);   

  server_addr.sin_port = htons(SERVER_PORT);   

  ////////////////////////////////////////////////////////////////////////////////////////////////////////////



  // 創建socket,若成功,返回socket描述符   

  //1、domain:即協議域,又稱為協議族(family)。AF_INET:TCP/IP協議簇

  //2、type:指定socket類型。SOCK_STREAM(常用)字節流套接字

  //3、protocol:故名思意,就是指定協議。0:IPPROTO_TCP TCP傳輸協議 

  int server_socket_fd = socket(PF_INET, SOCK_STREAM, 0);   

  if(server_socket_fd < 0)   

  {   

    perror("Create Socket Failed:");   

    exit(1);   

  }   

 

  //int getsockopt(int sock, int level, int optname, void *optval, socklen_t *optlen);

  //sock:將要被設置或者獲取選項的套接字。level:選項所在的協議層。

  //optname:需要訪問的選項名。optval:對於getsockopt(),指向返回選項值的緩沖。optlen:作為入口參數時,選項值的最大長度。

  // 令SO_REUSEADD==true 允許套接口和一個已在使用中的地址捆綁(參見bind())。

  int opt = 1;

  setsockopt(server_socket_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));   

    

  //bind綁定socket和socket地址結構   

  //三個參數為:socket描述符、協議地址、地址的長度

  if(-1 == (bind(server_socket_fd, (struct sockaddr*)&server_addr, sizeof(server_addr))))   

  {   

    perror("Server Bind Failed:");   

    exit(1);   

  }   

  //sockfd:第一個參數即為要監聽的socket描述符

  //backlog : 第二個參數為相應socket可以排隊的最大連接個數

  //socket()函數創建的socket默認是一個主動類型的,listen函數將socket變為被動類型的,等待客戶的連接請求。

  if(-1 == (listen(server_socket_fd, LENGTH_OF_LISTEN_QUEUE)))   

  {   

    perror("Server Listen Failed:");   

    exit(1);   

  }   

  printf("Socket Init Successful!  Begin to listen!\n");

///////////////////////////////////////////////////////////////////////////////////////////////////////////



  while(1)   

  {   

    // 定義客戶端的socket地址結構   

    struct sockaddr_in client_addr;   

    socklen_t client_addr_length = sizeof(client_addr);   

    

    //int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

    //sockfd:第一個參數為服務器的socket描述符

    //addr:,第二個參數為指向struct sockaddr *的指針,用於返回客戶端的協議地址

    //addrlen:第三個參數為協議地址的長度

    //返回值 : 如果accpet成功,那么其返回值是由內核自動生成的一個全新的描述字,代表與返回客戶的TCP連接。



    // 接受連接請求,返回一個新的socket(描述符),這個新socket用於同連接的客戶端通信   

    // accept函數會把連接到的客戶端信息寫到client_addr中   

    int session_fd = accept(server_socket_fd, (struct sockaddr*)&client_addr, &client_addr_length);   

    if(session_fd < 0)   

    {   

      perror("Server Accept Failed:");   

    //  break;   

    }   

    char client_addr_res[20];

    //char *ptr=inet_ntop(AF_INET, &client_addr.sin_addr, client_addr_res, strlen(client_addr_res));

    printf("Get Connected with Client:%s ,the port is :%d Opening a new Thread...\n",inet_ntoa(client_addr.sin_addr),client_addr.sin_port);

    pthread_t thread_id;

    if (pthread_create(&thread_id, NULL, (void *)(&Data_handle), (void *)(&session_fd)) == -1)

    {

        fprintf(stderr, "pthread_create error!\n");

        break;                                  //break while loop

    }

    

 

  }   

  // 關閉監聽用的socket   

  close(server_socket_fd);   

  return 0;   

}   



static void Data_handle(void * fd)

{

    int session_fd = *((int *)fd);

    // recv函數通過描述字讀取數據到字節流,並存儲到地址字符串

    char buffer[BUFFER_SIZE];

    bzero(buffer, BUFFER_SIZE);

    if (recv(session_fd, buffer, BUFFER_SIZE, 0) < 0)

    {

        perror("Server Recieve Data Failed:");

    }

    

    if(strcmp(buffer,"hi")==0)

        {

               bzero(buffer, BUFFER_SIZE);



               strcpy(buffer, "hello");

     

             if (send(session_fd, buffer, BUFFER_SIZE, 0) < 0)

            {

                printf("Send Failed./n");

            }



                       }



        else 

         {



               bzero(buffer, BUFFER_SIZE);



               strcpy(buffer, "i do not understand");



             if (send(session_fd, buffer,BUFFER_SIZE , 0) < 0)

            {

                printf("Send Failed./n");

            }



                       }



        close(session_fd);

    pthread_exit(NULL);   //terminate calling thread!

}

 

 

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM