基于嵌入式Linux多线程聊天系统的设计与实现学生姓名王宣达学号 S2******* 所在系(院)电子信息工程系专业名称电路与系统年级 2009级2011年8月3日中文摘要外文摘要目录1.引言 (1)2.Linux多线程聊天系统的设计思想 (3)2.1 聊天系统中服务器的设计思想 (3)2.2 聊天系统中客户端的设计思想 (3)3. Linux多线程聊天系统的实现过程 (5)3.1 多线程聊天系统中服务器端的实现过程 (5)3.2 多线程聊天系统中客户端的实现过程 (7)4.Linux多线程系统设计中出现的问题和解决的方法 (12)4.1 多线程中资源的释放问题 (12)4.2 (12)参考文献 (12)1.引言在80年代中期,线程技术就应用到了操作系统中,那时在一个进程中只允许有一个线程,这样多线程就意味着多进程,虽然实现了多任务,但是资源消耗还是非常可观的。
而到现在,多线程技术已经被许多操作系统所支持,有Windows/NT,还有Linux。
多线程和进程相比有两点优势:1.它是一种消耗资源非常少的多任务操作方式。
在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种消耗非常大的多任务工作方式。
而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,这样创建一个线程所占用的空间远远小于创建一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。
当然,随着系统的不同,这个差距也不不同。
2.线程间比进程间的通信机制更为便利。
对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。
线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。
当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,这时就要用到互斥锁机制来保证线程间的同步。
所以在本文的多线程聊天程序的设计中,采用多线程的方式设计系统更为适宜。
其中,系统中用到的操作主要是:线程操作,设置互斥锁。
其中,线程操作包括:线程创建,退出,。
设置互斥锁包括:创建互斥锁,加锁和解锁。
但是,要实现网络聊天,系统中还要用到linux下的网络编程。
Linux下的网络编程通过socket接口实现。
socket 是一种特殊的I/O,可以实现网络上的通信机制。
Socket也是一种文件描述符。
它具有一个类似于打开文件的函数调用Socket(),该函数返回一个整型的Socket描述符,随后的连接建立、数据传输等操作都是通过该Socket实现的。
常用的Socket类型有两种:流式Socket (SOCK_STREAM)和数据报式Socket(SOCK_DGRAM)。
流式是一种面向连接的Socket,针对于面向连接的TCP服务应用;数据报式Socket是一种无连接的Socket,对应于无连接的UDP服务应用。
本文使用流式套接字,来建立服务器和客户端的通信桥梁。
其中,系统用到的API有:socket,bind,listen,accept,connect系统调用,分别完成套接字的创建,绑定本地端口和本地IP形成三元的套接字,激活监听端口并创建一个请求队列,接受客户端connect连接请求,客户端发起连接请求等功能。
2.Linux多线程聊天系统的设计思想下面是服务器和客户端的设计思想:2.1 聊天系统中服务器的设计思想服务器应用程序先创建一个socket,socket就像一个文件描述符,用来引用系统分配给服务器进程的资源,并且这部分资源只能由该进程访问,对该进程是独占的。
服务器通过使用socket系统调用的方式创建socket,创建之后该socket不能被其它进程共享。
然后,服务器进程给刚刚创建的socket绑定一个名字。
本地的socket 会分配一个在Linux文件系统中的文件名。
对于网络socket,其名称通常是与特定网络相关的服务标识(如:端口号)以便其他客户程序连接,系统能够通过这个标识符将带有特定端口的访问请求指定给服务器进程。
Socket名称的绑定是通过bind系统调用实现的。
之后,服务器进程就会等待客户对该命名的socket发起链接请求。
使用listen系统调用创建一个请求队列,最后使用accept系统调用接受请求。
定义一个用于存放已经建立起连接的用户链表,每个节点包含用户名,用户所用的套接字,还有链表指针。
当Accept系统调用接受请求后就将新生成的五元套接字填充到用户的节点中,把节点加入链表,并且创建一个线程服务该用户,然后主线程循环执行accept响应下一个connect连接请求。
2.2 聊天系统中客户端的设计思想客户端首先获取用户输入的要连接的服务器的IP和端口号,然后用socket系统调用创建一个没有命名的socket,接着使用connect系统调用建立一个到服务器端命名的socket的连接,连接建立后,socket就像一个文件描述符一样可以进行读写操作,给建立起连接的双方提供双向通信。
这里要注意的是,在与服务器通信的同时,用户也许会在终端输入信息发给服务器,所以我们要同时监控socket和用户的标准输入。
实现的方式是当连接建立以后使用select系统调用同时监听上面两个文件描述符,当其中一个可用的时候select返回,然后用FD_ISSET系统调用依次判断具体是哪个文件描述符可用,当键盘有数据可用时,我们将键盘输入的内容原封不动地通过socket发送给服务器;当socket可用时,我们就把socket里读到的内容显示到客户的屏幕。
3. Linux多线程聊天系统的实现过程下面是聊天系统的具体实现过程,以及遇到的问题和解决的方案。
3.1 多线程聊天系统中服务器端的实现过程服务器主程序的主要实现代码如下://选择通信协议为IPv4sin.sin_family=AF_INET;//系统自动选择一个网卡的ip地址sin.sin_addr.s_addr=INADDR_ANY;//选择端口号sin.sin_port=htons(port);//创建监听套接字listen_fd=socket(AF_INET,SOCK_STREAM,0);{int opt=1;setsockopt(listen_fd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));}//绑定端口,ip到套接字ret=bind(listen_fd,(struct sockaddr *)&sin,sizeof(sin));if(ret<0){perror("bind");exit(1);}//激活监听,定义等待队列长度listen(listen_fd,T_MAX);printf("accepting connections......\n");while(1){// 接收套接字,绑定客户端IP,端口,创建五元套接字conn_fd=accept(listen_fd,(struct sockaddr *)&pin,&address_size);ptr=(node *)malloc(sizeof(node));//填充套接字ptr->sockfd=conn_fd;//填充默认名字strcpy(ptr->user_name,"unknow");ptr->next=NULL;//插入链表insert(head,ptr);pthread_create(&tid[i], NULL, (void*)thread, ptr);i++;}}下面是服务器运行起来的效果图3.2 多线程聊天系统中客户端的实现过程服务器主程序的主要实现代码如下://屏蔽信号的干扰signal(SIGPIPE,SIG_IGN);signal(SIGCHLD,SIG_IGN);printf ("Enter server ip:");fgets(ip_buf, 20, stdin);*(strchr(ip_buf, '\n')) = '\0';printf ("Enter server port:");fgets(port_buf, 10, stdin);*(strchr(port_buf, '\n')) = '\0';//把字符串转换成整型port = atoi(port_buf);bzero(&pin,sizeof(pin));pin.sin_family=AF_INET;//由于用于网络的参数是大端序列,而PC机是小端序列,所以要把传递的IP包的参数转换成网络字节序inet_pton(AF_INET,ip_buf,&pin.sin_addr);pin.sin_port=htons(port);sock_fd=socket(AF_INET,SOCK_STREAM,0);rst = connect(sock_fd,(void*)&pin,sizeof(pin));if (0 != rst){perror("connect");exit(1);}printf ("welcome to chatroom! Enter help!\n");head.sockfd=sock_fd;while (1) {fd_set rset;int tmp;FD_ZERO(&rset);//将描述符head.sockfd加入到描述符集rset中FD_SET(head.sockfd, &rset);FD_SET(fileno(stdin), &rset);//用select系统调用选择可用的文件描述符,第一个参数是读或者写,或者既不可读也不可写,中文件描述符最大的+1//第二个参数是读集合,第三个是写集合,第四个是超时时间tmp = select((head.sockfd > fileno(stdin) ? head.sockfd : fileno(stdin)) + 1, &rset, NULL, NULL, NULL);if (tmp == -1) {perror("select");continue;}if (FD_ISSET(fileno(stdin), &rset)) {if ((fgets(buff,sizeof(buff),stdin) == NULL) && ferror(stdin)) {perror("fgets");} else {send(head.sockfd,buff,strlen(buff),0);}}if (FD_ISSET(head.sockfd, &rset)) {//memset(buff,0,sizeof(buff));int n = read(head.sockfd,buff,MAX);if(n<=0) {perror("closed");exit(0);} else {write(fileno(stdout), buff, n);}}}以下是客户端程序运行过程:1.输入服务器的IP和服务器端口进入聊天程序命令行界面(因为演示时是用一台机器做服务器又做客户端,所以IP为本地回环127.0.0.1)2.注册在虚拟终端pts/1上注册一个用户在pts/2 上注册另一个用户3.登陆4.显示在线用户5.群聊用户Haoyongxin端:用户wangxuanda端:6.私聊用户wangxuanda端用户haoyongxin端4.Linux多线程系统设计中出现的问题和解决的方法4.1 多线程中资源的释放问题在Linux平台下线程的结束问题:如何让线程的资源在其结束后可以得到合理的释放。