功能描述:程序應用多線程技術,可是實現1對N進行網絡通信聊天。但至今沒想出合適的退出機制,除了用Ctr+C。出于演示目的,這里采用UNIX域協議(文件系統套接字),程序分為客戶端和服務端。應用select函數來實現異步的讀寫操作。
先說一下服務端:首先先創建套接字,然后綁定,接下進入一個無限循環,用accept函數,接受“連接”請求,然后調用創建線程函數,創造新的線程,進入下一個循環。這樣每當有一個新的“連接”被接受都會創建一個新的線程,實現1對N的網絡通信。在服務端程序中線程中用一個buffer讀寫,為了避免錯誤,這時就要給關鍵代碼加上互斥鎖work_mutex,具體見代碼。
服務端代碼
1
#include
<
stdio.h
>
2
#include
<
stdlib.h
>
3
#include
<
string
.h
>
4
#include
<
pthread.h
>
5
#include
<
sys
/
socket.h
>
6
#include
<
sys
/
un.h
>
7
#include
<
unistd.h
>
8
#include
<
semaphore.h
>
//
這里沒有用二進制信號量可以刪掉
9
?
10
?
char
buffer[
1024
];
//
讀寫用的區域
11
sem_t bin_sem;
//
沒用到的二進制信號量,可以刪掉
12
void
*
pthread_function(
void
*
arg);
//
線程入口函數聲明
13
pthread_mutex_t work_mutex;
//
聲明互斥鎖
14
15
int
main(){
16
int
result;
//
整數變量用來儲存調用函數的返回值
17
struct
sockaddr_un server_address, client_address;
//
UNIX域的套接字,server_address用于服務端的監聽,client_address用于客戶端連接后的套接字
18
int
client_len;
//
連接后,accept函數會把客戶端的地址的長度儲存在這
19
int
server_socketfd, client_socketfd;
//
服務端和客戶端的套接字文件描述符
20
pthread_t a_thread;
//
線程ID標志
21
pthread_attr_t thread_attr;
//
線程的屬性,后面可以看的,被我注釋掉了,沒用到,可以刪掉。
22
23
result
=
sem_init(
&
bin_sem,
0
,
1
);
//
初始化二進制信號量,因為用了互斥鎖,所以沒用到,可以刪掉
24
if
(result
!=
0
){
25
perror(
"
sem_init
"
);
26
exit(EXIT_FAILURE);
27
}
28
29
result
=
pthread_mutex_init(
&
work_mutex, NULL);
//
初始化互斥鎖
30
if
(result
!=
0
){
31
perror(
"
pthread_mutex_init
"
);
32
exit(EXIT_FAILURE);
33
}
34
35
server_socketfd
=
socket(AF_UNIX, SOCK_STREAM,
0
);
//
創建套接字,用TCP連接方式,出于演示目的只用UNIX域套接字。
36
37
server_address.sun_family
=
AF_UNIX;
38
strcpy(server_address.sun_path,
"
server_socket
"
);
39
40
unlink(
"
server_socket
"
);
//
在綁定之前,把以前存在當前目錄下的套接字刪除
41
42
result
=
bind(server_socketfd, (
struct
sockaddr
*
)
&
server_address,
sizeof
(server_address));
//
綁定
43
if
(result
!=
0
){
44
perror(
"
bind
"
);
45
exit(EXIT_FAILURE);
46
}
47
48
result
=
listen(server_socketfd,
5
);
//
監聽,最多允許5個連接請求
49
if
(result
!=
0
){
50
perror(
"
listen
"
);
51
exit(EXIT_FAILURE);
52
}
53
54
client_len
=
sizeof
(client_address);
55
while
(
1
){
//
開始進入無限循環
56
/*
printf("If you want to quit, please enter 'quit'\n");
57
printf("Do you want to accept a connectiong\n");
58
memset(buffer, '\0', sizeof(buffer));
59
fgets(buffer, sizeof(buffer), stdin);
60
if((strncmp("quit", buffer, 4))==0) break;
*/
61
62
client_socketfd
=
accept(server_socketfd, (
struct
sockaddr
*
)
&
client_address,
&
client_len);
//
接受一個連接請求
63
64
/*
result = pthread_attr_init(&thread_attr);
65
if(result != 0){
66
perror("pthread_attr_init");
67
exit(EXIT_FAILURE);
68
}
69
result = pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED);
70
if(result != 0){
71
perror("pthread_attr_setdetachstate");
72
exit(EXIT_FAILURE);
73
}
*/
74
result
=
pthread_create(
&
a_thread, NULL, pthread_function, (
void
*
)client_socketfd);
//
成功接受一個請求后,就會創建一個線程,然后主線程又進入accept函數,如果此時沒有連接請求,那么主線程會阻塞
75
if
(result
!=
0
){
76
perror(
"
pthread_create
"
);
77
exit(EXIT_FAILURE);
78
}
79
80
}
81
}
82
83
void
*
pthread_function(
void
*
arg){
//
線程入口函數,每調用一次pthread_create,都會創建一個新的線程
84
int
fd
=
(
int
) arg;
//
把函數參數,即連接成功后的套接字,賦給fd.
85
int
result;
86
fd_set read_fds;
//
文件描述符集合,用于select函數
87
int
max_fds;
//
文件描述符集合的最大數
88
89
printf(
"
%d id has connected!!\n
"
, fd);
90
while
(
1
){
91
92
FD_ZERO(
&
read_fds);
//
清空集合
93
FD_SET(
0
,
&
read_fds);
//
將標準輸入放入監聽的文件描述符集合, 這個用于讀取標準輸入,即鍵盤的輸入
94
FD_SET(fd,
&
read_fds);
//
將連接后的客戶文件描述符放入監聽的文件描述符集合, 這個用于向客戶端讀取數據
95
max_fds
=
fd
+
1
;
96
97
//
sem_wait(&bin_sem);
98
pthread_mutex_lock(
&
work_mutex);
//
對關鍵區域上鎖
99
printf(
"
%d has get the lock\n
"
, fd);
100
result
=
select(max_fds,
&
read_fds, (fd_set
*
)NULL, (fd_set
*
)NULL, (
struct
timeval
*
)NULL);
//
開始監聽那些文件描述符出于可讀狀態
101
if
(result
<
1
){
102
printf(
"
select
"
);
103
}
104
if
(FD_ISSET(
0
,
&
read_fds)){
//
如果標準輸入處于可讀狀態,說明鍵盤有所輸入,將輸入的數據存放在buffer中,然后向客戶端寫回,如果輸入“quit”將會退出一個聊天線程
105
memset(buffer,
'
\0
'
,
sizeof
(buffer));
//
保險起見,清零
106
fgets(buffer,
sizeof
(buffer), stdin);
107
if
((strncmp(
"
quit
"
, buffer,
4
))
==
0
){
108
printf(
"
You have terminaled the chat\n
"
);
109
//
sem_post(&bin_sem);
110
pthread_mutex_unlock(
&
work_mutex);
111
break
;
112
}
113
else
{
114
result
=
write(fd, buffer,
sizeof
(buffer));
115
if
(result
==-
1
){
116
perror(
"
write
"
);
117
exit(EXIT_FAILURE);
118
}
119
}
120
}
121
if
(FD_ISSET(fd,
&
read_fds)){
//
如果客戶套接字符可讀,那么讀取存放在buffer中,然后顯示出來,如果對方中斷聊天,那么result==0
122
memset(buffer,
'
\0
'
,
sizeof
(buffer));
123
result
=
read(fd, buffer,
sizeof
(buffer));
124
if
(result
==
-
1
){
125
perror(
"
read
"
);
126
exit(EXIT_FAILURE);
127
}
128
else
if
(result
==
0
){
129
printf(
"
The other side has terminal the chat\n
"
);
130
//
sem_post(&bin_sem);
131
pthread_mutex_unlock(
&
work_mutex);
132
break
;
133
}
134
else
{
135
printf(
"
receive message: %s
"
, buffer);
136
}
137
}
138
pthread_mutex_unlock(
&
work_mutex);
//
解鎖
139
sleep (
1
);
//
如果沒有這一行,當前線程會一直占據buffer.讓當前線程暫停一秒可以實現1對N的功能。
140
//
sem_post(&bin_sem);
141
//
sleep (1);
142
}
143
//
printf("I am here\n");
144
close(fd);
145
pthread_exit(NULL);
146
147
}
148
讀者可以對比一下http://blog.csdn.net/hwz119/archive/2007/03/19/1534233.aspx
讀者可以發現,鏈接網絡中的程序需要結束當前一個聊天才能進行下一個聊天,而這個服務端可以同時對N個人進行聊天,盡管有些bug(如果客戶端對方回復太快太頻繁,服務端的鎖就會切換來切換去,無法回復到正確的客戶端)。
客戶端跟服務端很像,但比較簡單。這里面就不注釋了。這兩個程序我都運行過。。。沒什么基本大的問題。。但是功能很不完善。。。還需改進。。。。。
客戶端代碼
1
#include
<
stdio.h
>
2
#include
<
stdlib.h
>
3
#include
<
sys
/
socket.h
>
4
#include
<
sys
/
un.h
>
5
#include
<
string
.h
>
6
#include
<
sys
/
types.h
>
7
#include
<
sys
/
time.h
>
8
9
int
main(){
10
int
result;
11
int
socketfd;
12
int
len;
13
struct
sockaddr_un address;
14
fd_set read_fds, test_fds;
15
int
fd;
16
int
max_fds;
17
char
buffer[
1024
];
18
19
socketfd
=
socket(AF_UNIX, SOCK_STREAM,
0
);
20
21
address.sun_family
=
AF_UNIX;
22
strcpy(address.sun_path,
"
server_socket
"
);
23
len
=
sizeof
(address);
24
25
result
=
connect(socketfd, (
struct
sockaddr
*
)
&
address, len);
26
if
(result
==
-
1
){
27
perror(
"
connect
"
);
28
exit(EXIT_FAILURE);
29
}
30
31
FD_ZERO(
&
read_fds);
32
FD_SET(
0
,
&
read_fds);
33
FD_SET(socketfd,
&
read_fds);
34
max_fds
=
socketfd
+
1
;
35
36
printf(
"
Chat now!!\n
"
);
37
38
while
(
1
){
39
test_fds
=
read_fds;
40
result
=
select(max_fds,
&
test_fds, (fd_set
*
)NULL, (fd_set
*
)NULL, (
struct
timeval
*
)NULL);
41
if
(result
<
1
){
42
perror(
"
select
"
);
43
exit(EXIT_FAILURE);
44
}
45
46
if
(FD_ISSET(
0
,
&
test_fds)){
47
memset(buffer,
'
\0
'
,
sizeof
(buffer));
48
//
printf("send:");
49
fgets(buffer,
sizeof
(buffer), stdin);
50
if
((strncmp(
"
quit
"
, buffer,
4
))
==
0
){
51
printf(
"
\nYou are going to quit\n
"
);
52
break
;
53
}
54
result
=
write(socketfd, buffer,
sizeof
(buffer));
55
if
(result
==
-
1
){
56
perror(
"
write
"
);
57
exit(EXIT_FAILURE);
58
}
59
}
60
if
(FD_ISSET(socketfd,
&
test_fds)){
61
memset(buffer,
'
\0
'
,
sizeof
(buffer));
62
result
=
read(socketfd, buffer,
sizeof
(buffer));
63
if
(result
==
-
1
){
64
perror(
"
read
"
);
65
exit(EXIT_FAILURE);
66
}
else
if
(result
==
0
){
67
printf(
"
The other side has termianl chat!\n
"
);
68
break
;
69
}
else
{
70
printf(
"
recieve: %s
"
, buffer);
71
}
72
}
73
}
74
close(socketfd);
75
exit(EXIT_SUCCESS);
76
}
77
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061
微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元

