Repository navigation
Expand file tree
/
Copy pathTestServer.c
More file actions
200 lines (172 loc) · 8.3 KB
/
Copy pathTestServer.c
File metadata and controls
200 lines (172 loc) · 8.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
#define _CRT_SECURE_NO_WARNINGS
#pragma warning(disable : 4996)
#include <stdio.h>
#include <winsock2.h>
#include <windows.h>
#include <pthread.h>
#define MAX_CONNECT 100
#pragma comment(lib,"ws2_32.lib")
#pragma comment(lib,"pthreadVC2.lib")
typedef struct client_list_node
{
SOCKET socket_client; //客戶端的socket
struct sockaddr_in c_sin; //用於儲存已連線的客戶端的socket基本資訊
int is_run; //標記這個節點的socket是否正在被使用
HANDLE h; //為這個socket建立的執行緒 的控制代碼
}client_list_node_st, * client_list_node_t;
static client_list_node_st client_list[MAX_CONNECT] = { 0 }; //客戶端列表
static SOCKET socket_of_server; //服務端(本地)的socket
static struct sockaddr_in s_sin; //用於儲存本地建立socket的基本資訊
static int client_count = 0; //當前連線伺服器的客戶端的個數
static void analysis(char* data, int datal, client_list_node_t node_t);
DWORD WINAPI myfun1(LPVOID lpParameter); //宣告執行緒函式
int main(int argc, char* argv[])
{
int port = 6666;
int i = 0;
WORD socket_version = MAKEWORD(2, 2);
WSADATA wsadata;
if (WSAStartup(socket_version, &wsadata) != 0)
{
return 0;
}
if (argc > 1) //埠號可以通過啟動引數配置,沒有配置啟動引數的時候預設埠6666
{
port = atoi(argv[1]);
}
socket_of_server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//建立socket 並判斷是否建立成功
if (socket_of_server == INVALID_SOCKET)
{
printf("socket error\n");
return 0;
}
s_sin.sin_family = AF_INET; //定義協議族為IPV4
s_sin.sin_port = htons(port);//規定埠號
s_sin.sin_addr.S_un.S_addr = INADDR_ANY;
/************************************************************************
s_sin.sin_addr.S_un.S_addr = INADDR_ANY; 是在設定繫結在本機的哪個IP地址上,
在哪個IP地址上進行監聽。設定為INADDR_ANY代表0.0.0.0就是預設IP。
正常個人程式設計時 這個地方無關緊要。但若真正應用時這個地方最好設定清楚。
因為好多伺服器是多個網絡卡,本機有多個IP。哪個網絡卡是連線服務所在區域網的就要
設定為哪個。
************************************************************************/
if (bind(socket_of_server, (LPSOCKADDR)&s_sin, sizeof(s_sin)) == SOCKET_ERROR)//繫結
{
printf("bind error\n");
}
if (listen(socket_of_server, 5) == SOCKET_ERROR)//監聽
{
printf("listen error\n");
return 0;
}
printf("服務已經開啟 PORT:%d,正在等待連線... ...\n", port);
while (1)
{
SOCKET socket_of_client; //客戶端(遠端)的socket
struct sockaddr_in c_sin; //用於儲存已連線的客戶端的socket基本資訊
int c_sin_len; //函式accept的第三個引數,c_sin的大小。
c_sin_len = sizeof(c_sin);
socket_of_client = accept(socket_of_server, (SOCKADDR*)&c_sin, &c_sin_len);
/************************************************************************
沒有新的連線是 程式不會一直在這裡迴圈。此時accept會處於阻塞狀態。
直到有新的連線,或者出現異常。
************************************************************************/
if (socket_of_client == INVALID_SOCKET)
{
printf("accept error\n");
continue; //繼續等待下一次連線
}
else
{
if (client_count + 1 > MAX_CONNECT)
{
send(socket_of_client, "連線超限制,您已被斷開 \n", strlen("連線超限制,您已被斷開 \n"), 0);
printf("有新的客戶端請求連入IP:%s PORT:%d,但由於伺服器已經達到最大連線數,該裝置已經被強行斷開\n", inet_ntoa(c_sin.sin_addr), c_sin.sin_port);
Sleep(1000);
closesocket(socket_of_client);
continue;
}
else
{
int j = 0;
for (j = 0; j < MAX_CONNECT; j++)
{
if (client_list[j].is_run == 0)
{
client_list[j].is_run = 1;
client_list[j].socket_client = socket_of_client;
client_list[j].c_sin;
memcpy(&(client_list[j].c_sin), &c_sin, sizeof(c_sin));
if (client_list[j].h)
{
CloseHandle(client_list[j].h);
}
client_list[j].h = CreateThread(NULL, 0, myfun1, &(client_list[j]), 0, NULL);
client_count++;
break;
}
}
}
}
}
closesocket(socket_of_server);
WSACleanup();
return 0;
}
static void analysis(char* data, int datal, client_list_node_t node_t)
{
printf("客戶端(%s:%d)發來資料:%s 資料長度:%d\n", inet_ntoa(node_t->c_sin.sin_addr), node_t->c_sin.sin_port, data, datal);
//在這裡我們可以對已接收到的資料進行處理
//一般情況下這裡都是處理“粘包”的地方
//解決粘包之後 將完整的資料傳送給資料處理函式
}
DWORD WINAPI myfun1(LPVOID lpParameter)
{
char revData[256];//這個地方一定要酌情設定大小,這決定了每次能獲取多少資料
int ret;//recv函式的返回值 有三種狀態每種狀態的含義在下方有解釋
client_list_node_t node = (client_list_node_t)lpParameter;
printf("有新客戶端連入伺服器 , IP = %s PORT = %d \n", inet_ntoa(node->c_sin.sin_addr), node->c_sin.sin_port);
printf("最多可連線%d個客戶端,當前已連線%d個客戶端\n", MAX_CONNECT, client_count);
send(node->socket_client, "hello i am server \n", strlen("hello i am server \n"), 0);
while (1)
{
//接收來自 這個客戶端的訊息
ret = recv(node->socket_client, revData, 255, 0);
/************************************************************************
recv函式 的實質就是從socket的緩衝區裡拷貝出資料
返回值就是拷貝出位元組數的大小。
上面定義的載體(revData)大小是255,所以recv的第三個引數最大隻能設定為255,
如果設定為大於255的數值,當執行recv函式時恰好緩衝區的內容大於255,
就會導致記憶體洩漏,導致ret值小於零,解除阻塞狀態。因此這裡最好將第三個引數
設定為revData的大小,那麼當緩衝區內的資料小於255的時候
只需要執行一次recv就可以將緩衝區的內容都拷貝出來,但當緩衝區的資料大
於255的時候,就要執行多次recv函式。當緩衝區內沒有內容的時候,會處於阻塞
狀態,這個while函式會停在這裡。直到新的資料進來或者出現異常。
************************************************************************/
if (ret > 0)
{
revData[ret] = 0x00;//正常情況下不必這麼做,我這麼做只是為了能按字串的形式輸出它
analysis(revData, ret, node);
}
else if (ret == 0)
{
//當ret == 0 說明客戶端已斷開連線。這裡我們直接跳出迴圈去等待下次連線即可。
printf("客戶端斷開連線, IP = %s PORT = %d \n", inet_ntoa(node->c_sin.sin_addr), node->c_sin.sin_port);
closesocket(node->socket_client);
break;
}
else//ret < 0
{
//當ret < 0 說明出現了異常 例如阻塞狀態解除,或者讀取資料時出現指標錯誤等。
//所以我們這裡要主動斷開和客戶端的連結,然後跳出迴圈去等待新的連線。
printf("在與客戶端通訊是發生異常 IP = %s PORT = %d \n", inet_ntoa(node->c_sin.sin_addr), node->c_sin.sin_port);
closesocket(node->socket_client);
break;
}
}
node->is_run = 0;
client_count--;
printf("最多可連線%d個客戶端,當前已連線%d個客戶端\n", MAX_CONNECT, client_count);
return 0;
}