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)

一个服务程序通常在一个众所周知的地址监听对服务的请求,也就是说服务进程一直处于休眠状态,直到一个客户对这个服务的地址提出了连接请求。在这个时刻,服务程序被“唤醒”并且为客户提供服务—对客户的请求作出适当的反应。

客户机和服务器的运行过程如下:

 Linux Socket 下实现的网络聊天室_第1张图片

 

 

在实验一中,客户机的请求就是:发送给服务器一段没有处理过的源字符串,要求服务器进行处理

服务器的响应就是:发给客户机处理过的字符串

 

③   :使用socket编程在客户机和服务器之间建立连接和传输数据的流程

给出流程图:

 Linux Socket 下实现的网络聊天室_第2张图片

 

 

各个环节使用的代码会在后面代码分析时讲解

 

④:linux下基于c++的多线程编程

多线程是多任务处理的一种特殊形式,多任务处理允许让电脑同时运行两个或两个以上的程序。一般情况下,两种类型的多任务处理:基于进程和基于线程。

·      基于进程的多任务处理是程序的并发执行。

·      基于线程的多任务处理是同一程序的片段的并发执行。

 

多线程程序包含可以同时运行的两个或多个部分。这样的程序中的每个部分称为一个线程,每个线程定义了一个单独的执行路径。

创建线程

下面的程序,可以用它来创建一个 POSIX 线程:

    #include

    pthread_create (thread, attr, start_routine, arg)

在这里,pthread_create 创建一个新的线程,并让它可执行。下面是关于参数的说明:

 

创建线程成功时,函数返回 0,若返回值不为 0 则说明创建线程失败。

终止线程

使用下面的程序,可以用它来终止一个 POSIX 线程:

    #include

    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.  运行可执行程序查看实验效果:

 

实现多线程打开服务器

Linux Socket 下实现的网络聊天室_第3张图片

 

 

 

 

实现信息交互

 Linux Socket 下实现的网络聊天室_第4张图片

 

 

 

三:关键代码分析:

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  

#include  

#include   

#include  

#include   

#include 



#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

#include

#includein.h> 

#include   

#include  

#include    

#include  

#include<string.h>     

#include



#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!

}

 

 

 

 

 

 

 

 

你可能感兴趣的:(Linux Socket 下实现的网络聊天室)