當前位置:編程學習大全網 - 編程語言 - 如何編寫linux聊天室

如何編寫linux聊天室

自從開始學linux網絡編程後就想寫個聊天室,壹開始原本打算用多進程的方式來寫,可是發覺進程間的通信有點麻煩,而且開銷也大,後來想用多線程能不能實現呢,於是便去看了壹下linux裏線程的用法,實際上只需要知道 pthread_create 就差不多了,於是動手開幹,用了兩天時間,調試的過程挺痛苦的,壹開始打算用純C來擼,便用簡單的數組來存儲客戶端的連接信息,可是運行時出現了壹些很奇怪的問題,不知道是不是訪問了臨界資源,和線程間的互斥有關等等;奇怪的是,當改用STL的set或map時問題就解決了,但上網搜了下發現STL也不是線程安全的,至於到底是什麽問題暫時不想去糾結了,可能是其它壹些小細節的錯誤吧。先貼上代碼:

首先是必要的頭文件 header.h:

#ifndef ?__HEADER_H#define ?__HEADER_H#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <error.h>#include <signal.h>#include <sys/wait.h>#include <assert.h>#include <pthread.h>#define ?bool ?int ?// the 3 lines is for c originally#define ?true ? 1#define ?false ?0#define ?PORT ?9003#define ?BUF_LEN ?1024 ?// 緩沖區大小#define ?MAX_CONNECTION ?6 ?// 服務器允許的最大連接數,可自行更改#define ?For(i,s,t) ?for(i = (s); i != (t); ++i)#endif // __HEADER_H

然後是客戶端部分?client.cpp,相對來說簡單壹些:

#include "header.h"// 客戶端接收消息的線程函數void* recv_func(void *args)

{ char buf[BUF_LEN]; int sock_fd = *(int*)args; while(true) { int n = recv(sock_fd, buf, BUF_LEN, 0); if(n <= 0) ? break; ?// 這句很關鍵,壹開始不知道可以用這個來判斷通信是否結束,用了其它壹些很奇葩的做法來結束並關閉 sock_fd 以避免 CLOSE_WAIT 和 FIN_WAIT2 狀態的出現T.T write(STDOUT_FILENO, buf, n);

}

close(sock_fd);

exit(0);

}// 客戶端和服務端進行通信的處理函數void process(int sock_fd)

{

pthread_t td;

pthread_create(&td, NULL, recv_func, (void*)&sock_fd); ?// 新開個線程來接收消息,避免了壹讀壹寫的原始模式,壹開始竟把它放進 while 循環裏面了,淚崩。。。

char buf[BUF_LEN]; while(true) { int n = read(STDIN_FILENO, buf, BUF_LEN);

buf[n++] = '\0'; // 貌似標準讀入不會有字符串結束符的,需要自己手動添加

send(sock_fd, buf, n, 0);

}

close(sock_fd);

}int main(int argc, char *argv[])

{

assert(argc == 2); struct sockaddr_in cli;

bzero(&cli, sizeof(cli));

cli.sin_family = AF_INET;

cli.sin_addr.s_addr = htonl(INADDR_ANY);

cli.sin_port = htons(PORT); // 少了 htons 的話就連接不上了,因為小端機器的原因?

int sc = socket(AF_INET, SOCK_STREAM, 0); if(sc < 0) {

perror("socket error");

exit(-1);

}

inet_pton(AF_INET, argv[1], &(cli.sin_addr)); ? // 用第壹個參數作為連接服務器端的地址

int err = connect(sc, (struct sockaddr*)&cli, sizeof(cli)); if(err < 0) {

perror("connect error");

exit(-2);

}

process(sc);

close(sc); return 0;

}

最後是服務端 server.cpp:

#include <map>#include "header.h"using std::map;

map<int, struct sockaddr_in*> socks; // 用於記錄各個客戶端,鍵是與客戶端通信 socket 的文件描述符,值是對應的客戶端的 sockaddr_in 的信息// 群發消息給 socks 中的所有客戶端inline void send_all(const char *buf, int len)

{ for(auto it = socks.begin(); it != socks.end(); ++it)

send(it->first, buf, len, 0);

}// 服務端端接收消息的線程函數void* recv_func(void* args)

{ int cfd = *(int*)args; char buf[BUF_LEN]; while(true) { int n = recv(cfd, buf, BUF_LEN, 0); if(n <= 0) ? break; // 關鍵的壹句,用於作為結束通信的判斷 write(STDOUT_FILENO, buf, n); if(strcmp(buf, "bye\n") == 0) { // 如果接收到客戶端的 bye,就結束通信並從 socks 中刪除相應的文件描述符,動態申請的空間也應在刪除前釋放

printf("close connection with client %d.\n", cfd); free(socks[cfd]);

socks.erase(cfd); break;

}

send_all(buf, n); ? // 群發消息給所有已連接的客戶端 }

close(cfd); // 關閉與這個客戶端通信的文件描述符}// 和某壹個客戶端通信的線程函數void* process(void *argv)

{

pthread_t td;

pthread_create(&td, NULL, recv_func, (void*)argv); // 在主處理函數中再新開壹個線程用於接收該客戶端的消息

int sc = *(int*)argv; char buf[BUF_LEN]; while(true) { int n = read(STDIN_FILENO, buf, BUF_LEN);

buf[n++] = '\0'; // 和客戶端壹樣需要自己手動添加字符串結束符

send_all(buf, n); ? // 服務端自己的信息輸入需要發給所有客戶端 }

close(sc);

}int main(int argc, char *argv[])

{ struct sockaddr_in serv;

bzero(&serv, sizeof(serv));

serv.sin_family = AF_INET;

serv.sin_addr.s_addr = htonl(INADDR_ANY);

serv.sin_port = htons(PORT); int ss = socket(AF_INET, SOCK_STREAM, 0); if(ss < 0) {

perror("socket error"); return 1;

} int err = bind(ss, (struct sockaddr*)&serv, sizeof(serv)); if(err < 0) {

perror("bind error"); return 2;

}

err = listen(ss, 2); if(err < 0) {

perror("listen error"); return 3;

}

socks.clear(); ?// 清空 map

socklen_t len = sizeof(struct sockaddr); while(true) { struct sockaddr_in *cli_addr = (struct sockaddr_in*)malloc(sizeof(struct sockaddr_in)); int sc = accept(ss, (struct sockaddr*)cli_addr, &len); if(sc < 0) { free(cli_addr); continue;

} if(socks.size() >= MAX_CONNECTION) { // 當將要超過最大連接數時,就讓那個客戶端先等壹下

char buf[128] = "connections is too much, please waiting...\n";

send(sc, buf, strlen(buf) + 1, 0);

close(sc); free(cli_addr); continue;

}

socks[sc] = cli_addr; // 指向對應申請到的 sockaddr_in 空間

printf("client %d connect me...\n", sc);

pthread_t td;

pthread_create(&td, NULL, process, (void*)&sc); ? // 開壹個線程來和 accept 的客戶端進行交互 } return 0;

}

makefile文件:

all: server client

server: server.cpp

g++ -std=c++11 -o server server.cpp -lpthread

client: client.cpp

g++ -std=c++11 -o client client.cpp -lpthread

clean:

rm -f *.o

在我的ubuntu 14.04 64 位的機器上測試過沒有什麽問題,客戶端與服務端能正常的交互和退出,能通過服務端接收其它客戶端發送的消息,運行時cpu和內存占用情況正常,不會產生什麽奇怪的bug。暫時只寫了個終端的界面,客戶端的UI遲點再去弄吧~

*****************************************************************************************************************************************

今天試了下用 PyQt4 去寫個客戶端的界面,調了好壹天,總算能看到點東西了,先上圖:

而命令行下的客戶端(上面的 client.cpp 文件)的運行界面是這樣子的:

服務端的運行情況是:

PyQt4 編寫的客戶端(pyqt_client.py)代碼是:

#!/usr/bin/env python#-*- coding: utf-8 -*-from PyQt4 import QtGui, QtCoreimport sysimport socketimport threadclass Client(QtGui.QWidget):

BUF_LEN = 1024 def __init__(self, parent=None):

QtGui.QWidget.__init__(self, parent)

self.setWindowTitle(u'TCP客戶端')

self.resize(600, 500)

self.center()

layout = QtGui.QGridLayout(self)

label_ip = QtGui.QLabel(u'遠程主機IP:')

layout.addWidget(label_ip, 0, 0, 1, 1)

self.txt_ip = QtGui.QLineEdit('127.0.0.1')

layout.addWidget(self.txt_ip, 0, 1, 1, 3)

label_port = QtGui.QLabel(u'端口:')

layout.addWidget(label_port, 0, 4, 1, 1)

self.txt_port = QtGui.QLineEdit('9003')

layout.addWidget(self.txt_port, 0, 5, 1, 3)

self.isConnected = False

self.btn_connect = QtGui.QPushButton(u'連接')

self.connect(self.btn_connect, QtCore.SIGNAL( 'clicked()'), self.myConnect)

layout.addWidget(self.btn_connect, 0, 8, 1, 2)

label_recvMessage = QtGui.QLabel(u'消息內容:')

layout.addWidget(label_recvMessage, 1, 0, 1, 1)

self.btn_clearRecvMessage = QtGui.QPushButton(u'↓ 清空消息框')

self.connect(self.btn_clearRecvMessage, QtCore.SIGNAL( 'clicked()'), self.myClearRecvMessage)

layout.addWidget(self.btn_clearRecvMessage, 1, 7, 1, 3)

self.txt_recvMessage = QtGui.QTextEdit()

self.txt_recvMessage.setReadOnly(True)

self.txt_recvMessage.setStyleSheet('background-color:yellow')

layout.addWidget(self.txt_recvMessage, 2, 0, 1, 10)

lable_name = QtGui.QLabel(u'姓名(ID):')

layout.addWidget(lable_name, 3, 0, 1, 1)

self.txt_name = QtGui.QLineEdit()

layout.addWidget(self.txt_name, 3, 1, 1, 3)

self.isSendName = QtGui.QRadioButton(u'發送姓名')

self.isSendName.setChecked(False)

layout.addWidget(self.isSendName, 3, 4, 1, 1)

label_sendMessage = QtGui.QLabel(u' 輸入框:')

layout.addWidget(label_sendMessage, 4, 0, 1, 1)

self.txt_sendMessage = QtGui.QLineEdit()

self.txt_sendMessage.setStyleSheet("background-color:cyan")

layout.addWidget(self.txt_sendMessage, 4, 1, 1, 7)

self.btn_send = QtGui.QPushButton(u'發送')

self.connect(self.btn_send, QtCore.SIGNAL('clicked()'), self.mySend)

layout.addWidget(self.btn_send, 4, 8, 1, 2)

self.btn_clearSendMessage = QtGui.QPushButton(u'↑ 清空輸入框')

self.connect(self.btn_clearSendMessage, QtCore.SIGNAL( 'clicked()'), self.myClearSendMessage)

layout.addWidget(self.btn_clearSendMessage, 5, 6, 1, 2)

self.btn_quit = QtGui.QPushButton(u'退出')

self.connect(self.btn_quit, QtCore.

  • 上一篇:我想知道CAXA、CAD、SOLIDWORKS、PROE、UG、它們的發展順序?
  • 下一篇:PSP和MP5壹樣嗎
  • copyright 2024編程學習大全網