计算机网络作业(四)

对socket编程我还是挺熟悉的,毕竟用过python写的shadowsocks,不过那时候时候没看过这个程序的源码,最近作业是做一个最多支持5线程的迭代服务器,最好能够支持智能回复,看了一下智能回复应该能用图灵机器人的API搞定,画出来的程序流程都大概如下所示:

IMG_20171019_210925.jpg

第一章:

服务器端

界面

界面.png

启动服务器按钮对应代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void Csocket_serverDlg::OnBnClickedOk()
{
// TODO: 在此添加控件通知处理程序代码
CDialogEx::OnOK();
char *chs = m_edit_port.GetBuffer(0);
UpdateData(false);
char *a[] = { "main.exe",chs };
if (m_edit_port)
{
AfxMessageBox("服务器已经开始运行");
main(2,a);
}
else
AfxMessageBox("请输入端口号");
}

###头文件

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <io.h>
#include <process.h>
#include <winsock2.h>
#include <windows.h>
#include<ws2tcpip.h>
#include <iostream>
using namespace std;
#pragma comment(lib, "ws2_32.lib")
#define BUF_SIZE 5000
#define CO_THREAD_NUM 5

主要代码

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
void error_handling(char *message)
{
AfxMessageBox(message);
exit(1);

}
void WorkThread(LPVOID lpParam)
{
SOCKET sockListen = (SOCKET)lpParam;
SOCKET sockSvr;
fd_set readSet;
int ret;
timeval tv;
char buf[5000];
while (1)
{
sockSvr = accept(sockListen, NULL, NULL);
if (sockSvr == INVALID_SOCKET) {
AfxMessageBox("accept: %d ", WSAGetLastError());
continue;
}
while (1)
{
FD_ZERO(&readSet);
FD_SET(sockSvr, &readSet);
tv.tv_sec = 5;
tv.tv_usec = 0;
ret = select(0, &readSet, NULL, NULL, &tv);
if (ret == SOCKET_ERROR || ret == 0)
{
AfxMessageBox("Socket %d:Sekect error {%d} or Timeout!\n", sockSvr, WSAGetLastError());
break;
}
if (FD_ISSET(sockSvr, &readSet)) {
memset(buf, 0, 500);
ret = recv(sockSvr, buf, 5000, 0);
/*if (ret == SOCKET_ERROR || ret == 0) {
AfxMessageBox("recv error {%d} or Peer closed!\n", WSAGetLastError());
break;
}*/
ret = send(sockSvr, buf, strlen(buf), 0);
if (ret == SOCKET_ERROR)
break;
}
}
closesocket(sockSvr);
}
}
int main(int argc, char *argv[])
{
WSADATA Ws;
int serv_sock;//服务器端socket
if (WSAStartup(MAKEWORD(2, 2), &Ws) != 0)
{
error_handling("WSAStartup() error!");
}
sockaddr_in serv_addr;
// socklen_t clnt_addr_size;
if (argc != 2)
{
printf("Usage : %s <port>\n", argv[0]);
exit(1);
}
serv_sock = socket(AF_INET, SOCK_STREAM, 0);
BOOL bReuseAddr = true;
setsockopt(serv_sock, SOL_SOCKET, SO_REUSEADDR, (char *)&bReuseAddr, sizeof(bReuseAddr));

if (serv_sock == -1)
{
error_handling("socket() error");
}
memset(&serv_addr, 0, sizeof(serv_addr));//serv_addr全部置零,清楚变量值
serv_addr.sin_family = AF_INET;//表示使用ip地址
serv_addr.sin_addr.s_addr = INADDR_ANY;//INADDR_ANY表示我不在意Local IP,由系統自行決定
//若要指定IP,則使用inet_addr,例如:name.sin_addr.s_addr = inet_addr(“140.115.65.1”);
//將IP字串轉為網路位元排列,如需要反轉換,有inet_ntoa函式可用
serv_addr.sin_port = htons(atoi(argv[1]));//绑定端口
if (bind(serv_sock, (sockaddr*)&serv_addr, sizeof(serv_addr)) == SOCKET_ERROR)//绑定
{
error_handling("bind() error");
WSACleanup();
closesocket(serv_sock);
return -1;
}
if (listen(serv_sock, 5) == SOCKET_ERROR)//对serv_sock进行监听,5表示允许5个用户请求
{
error_handling("listen() error");
WSACleanup();
closesocket(serv_sock);
return -1;
}
for (int i = 0; i < CO_THREAD_NUM; i++)
_beginthread(WorkThread, 0, (LPVOID)serv_sock);
// clnt_addr_size = sizeof(clnt_addr);
//int nSize = sizeof(SOCKADDR);
/*char buffer[BUF_SIZE] = { 0 }; //缓冲区
for (int i = 0; i < 5; i++) {*/
//clnt_sock = accept(serv_sock, (sockaddr*)&clnt_addr, &clnt_addr_size);//serv_sock接受
//int strLen = recv(clnt_sock, buffer, BUF_SIZE, 0); //接收客户端发来的数据
//send(clnt_sock, message, sizeof(message), 0); //将数据原样返回
//closesocket(clnt_sock); //关闭套接字
//memset(buffer, 0, BUF_SIZE); //重置缓冲区
//}
/*if (clnt_sock == INVALID_SOCKET)
{
error_handling("accept() error");
}
send(clnt_sock, message, sizeof(message), 0);
closesocket(clnt_sock);*/
Sleep(INFINITE);

closesocket(serv_sock);
WSACleanup();
return 0;
}

客户端

控制台客户端(已实现)

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
// socket_test_cilent.cpp: 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <io.h>
#include <process.h>
#include <winsock2.h>
#include <windows.h>
#include<ws2tcpip.h>
#include <iostream>
#pragma comment(lib, "ws2_32.lib")
#include <Ws2tcpip.h>

using namespace std;

#define BUF_SIZE 5000


void ErrorHandling(char* message);

int main(int argc, char *argv[])
{
SOCKET sock;
char bufSend[BUF_SIZE];
char bufRecv[BUF_SIZE];
sockaddr_in sockAddr;
char message[30];
if (argc != 3)
{
printf("Usage : %s <IP> <port>\n ", argv[0]);
exit(1);
}
//初始化DLL
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
ErrorHandling("WSAStartup() error!");
//向服务器发起请求

sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET)
{
WSACleanup();
ErrorHandling("socket() error");

}
memset(&sockAddr, 0, sizeof(sockAddr)); //每个字节都用0填充
sockAddr.sin_family = PF_INET;
// sockAddr.sin_addr.s_addr = inet_addr(argv[1]);

InetPton(AF_INET, argv[1], &sockAddr.sin_addr);
sockAddr.sin_port = htons(atoi(argv[2]));
if (connect(sock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR)) == SOCKET_ERROR)
{
if (sock != INVALID_SOCKET)
closesocket(sock);
WSACleanup();
ErrorHandling("connect() error");
}
else
{
printf("成功连接");
}
//获取用户输入的字符串并发送给服务器
printf("请输入要向服务器发送的信息:\n");
fd_set readSet;
struct timeval tv;
int ret, len;
while (1)
{

memset(bufSend, 0, 1000);
gets_s(bufSend);
if (!strcmp(bufSend, "quit\n") || !strcmp(bufSend, "QUIT\n") || !strcmp(bufSend, "Quit\n"))
{

WSACleanup(); //终止使用 DLL
printf("你已经退出");
exit(0);
}
len = strlen(bufSend);
if (len > 997)
len = 997;
bufSend[len] = '\r';
bufSend[len + 1] = '\n';
bufSend[len + 2] = 0;
ret = send(sock, bufSend, strlen(bufSend), 0);
if (ret == SOCKET_ERROR) {
printf("send:%d\n", WSAGetLastError());
break;
}
FD_ZERO(&readSet);
FD_SET(sock, &readSet);
tv.tv_sec = 3;
tv.tv_usec = 0;
ret = select(0, &readSet, NULL, NULL, NULL);
if (ret == SOCKET_ERROR) {
printf("select: %d", WSAGetLastError());
break;
}
if (ret == 0) {
printf("TimeOut,No Response From Server\n");
break;
}
if (FD_ISSET(sock, &readSet)) {
memset(bufRecv, 0, 1000);
ret = recv(sock, bufRecv, 1000, 0);
if (ret == SOCKET_ERROR) {
printf("recv: %d\n", WSAGetLastError());
break;
}
else
{
printf("%s\n", bufRecv);
}
}
if (sock != INVALID_SOCKET)
{
closesocket(sock);
}
}
WSACleanup();
printf("Stopped.\n");
return 0;

}
void ErrorHandling(char* message)
{
fputs(message,stderr);
fputc('\n', stderr);
exit(1);
}

测试gif

服务器端测试.gif

调用图灵机器人(暂未实现)

能力有限,不知道怎么整合MFC程序和这个winhttp的程序整合

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

#include <iostream>
#include <Windows.h>
#include <winhttp.h>
#pragma comment(lib,"winhttp.lib")

#define TULING_URL L"www.tuling123.com/openapi/api?key=这里换成你自己从图灵申请的API啦&info=%s"
static wchar_t String[1024];

//编码转换
char *UnicodeToANSI(const wchar_t *str)
{
static char result[1024];
int len = WideCharToMultiByte(CP_ACP, 0, str, -1, NULL, 0, NULL, NULL);
WideCharToMultiByte(CP_ACP, 0, str, -1, result, len, NULL, NULL);
result[len] = '\0';
return result;
}
wchar_t *UTF8ToUnicode(const char *str)
{
static wchar_t result[1024];
int len = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);
MultiByteToWideChar(CP_UTF8, 0, str, -1, result, len);
result[len] = L'\0';
return result;
}
wchar_t *ANSIToUnicode(const char* str)
{
int textlen;
static wchar_t result[1024];
textlen = MultiByteToWideChar(CP_ACP, 0, str, -1, NULL, 0);
memset(result, 0, sizeof(char) * (textlen + 1));
MultiByteToWideChar(CP_ACP, 0, str, -1, (LPWSTR)result, textlen);
return result;
}

bool GetHttpPage(void)
{
DWORD dwSize = 0;
DWORD dwDownloaded = 0;
LPSTR pszOutBuffer = NULL;
static HINTERNET hSession = WinHttpOpen(L"A Tuling API Example Program/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
static HINTERNET hConnect = NULL, hRequest = NULL;
BOOL bResults = FALSE;

//从控制台读出一行文字,注意读出来的内容是ANSI编码的,我们需要转换成 Unicode编码
static char uin[1024]; gets_s(uin);
wsprintf(String, TULING_URL, ANSIToUnicode(uin));

//建立一个http的连接会话,给出主机名就行,可以域名,也可以是IP地址,不需要http;前缀
if (hSession)
{
if (!hConnect)
hConnect = WinHttpConnect(hSession, L"www.tuling123.com", INTERNET_DEFAULT_HTTP_PORT, 0);
}

//创建一个HTTP请求句柄
if (hConnect)
hRequest = WinHttpOpenRequest(hConnect, L"GET", String, NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_ESCAPE_PERCENT | WINHTTP_FLAG_REFRESH);

//发送请求数据
if (hRequest)
bResults = WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0);

// 请求结束,接收数据
if (bResults)
bResults = WinHttpReceiveResponse(hRequest, NULL);
else
printf("Error %d has occurred.\n", GetLastError());
//如果返回值为false,可以使用getlasterror来得到错误信息,下同
//返回值的详细信息可以看微软网页,或者看这里翻译好的中文接口说明
//http://blog.csdn.net/fengsh998/article/details/8201591

// 内部使用的一个循环来确保能接受到所有数据
if (bResults)
{
do
{
//检查是否还有数据需要接收
dwSize = 0;
if (!WinHttpQueryDataAvailable(hRequest, &dwSize))
{
printf("Error %u in WinHttpQueryDataAvailable.\n", GetLastError());
break;
}

if (!dwSize)
break;

//为缓冲分配内存并读取
pszOutBuffer = new char[dwSize + 1];

if (!pszOutBuffer)
{
printf("Out of memory\n");
break;
}

ZeroMemory(pszOutBuffer, dwSize + 1);

if (!WinHttpReadData(hRequest, (LPVOID)pszOutBuffer, dwSize, &dwDownloaded))
{
printf("Error %u in WinHttpReadData.\n", GetLastError());
}
else
{
//图灵api返回来的内容使用的是UTF-8编码,我们需要把它转换回ANSI才能在控制台显示
//printf("return:%s\n", UnicodeToANSI(UTF8ToUnicode(pszOutBuffer)) );

//因为没有使用JSON库,所以我暴力拆了这字符串。
pszOutBuffer[strlen(pszOutBuffer)-2] = '\0';
printf("小灵:%s\n\n", UnicodeToANSI(UTF8ToUnicode(pszOutBuffer)) + 23);
return true;
}

delete[] pszOutBuffer;
if (!dwDownloaded)
break;

} while (dwSize > 0);
}

//收尾,关闭被打开的句柄
if (hRequest) WinHttpCloseHandle(hRequest);
if (hConnect) WinHttpCloseHandle(hConnect);
if (hSession) WinHttpCloseHandle(hSession);

return false;
}

int main(void)
{
system("color F0");
system("title 会聊天的图灵机器人 ●﹏●");
printf("\n 我是小灵,快来和我聊天吧! ●▽●\n\n");

do{ printf("我:"); } while (GetHttpPage());

system("pause");
return 0;
}

客户端带界面版本(有问题,未调试)

客户端界面.png
发送按钮对应的实现代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void Csocket_clientDlg::OnBnClickedButton2()
{
// TODO: 在此添加控件通知处理程序代码
UpdateData(true);
char *ip = m_edit_servaddr.GetBuffer(0);
char *port = m_edit_servport.GetBuffer(0);
char *argv[] = { "main.exe",ip,port };
CString str;
m_chatedit.GetWindowTextA(str);
string str1 = str.GetBuffer(0);
strcpy_s(bufSend, str1.c_str());
UpdateData(false);
main(3, argv);
}

主要代码

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

void ErrorHandling(char* message);

int main(int argc, char *argv[])
{
Csocket_clientDlg mfc;
SOCKET sock;
sockaddr_in sockAddr;
if (argc != 3)
{
//printf("Usage : %s <IP> <port>\n ", argv[0]);
AfxMessageBox("Usage : main.exe <IP> <port>\n ");

exit(1);
}
//初始化DLL
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
ErrorHandling("WSAStartup() error!");
//向服务器发起请求

sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET)
{
WSACleanup();
ErrorHandling("socket() error");

}
memset(&sockAddr, 0, sizeof(sockAddr)); //每个字节都用0填充
sockAddr.sin_family = PF_INET;
// sockAddr.sin_addr.s_addr = inet_addr(argv[1]);

InetPton(AF_INET, argv[1], &sockAddr.sin_addr);
sockAddr.sin_port = htons(atoi(argv[2]));
if (connect(sock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR)) == SOCKET_ERROR)
{
if (sock != INVALID_SOCKET)
closesocket(sock);
WSACleanup();
ErrorHandling("connect() error");
}
else
{
//printf("成功连接");
AfxMessageBox("成功连接");
}
fd_set readSet;
struct timeval tv;
int ret, len;
while (1)
{
if (!strcmp(bufSend, "quit\n") || !strcmp(bufSend, "QUIT\n") || !strcmp(bufSend, "Quit\n"))
{

WSACleanup(); //终止使用 DLL
AfxMessageBox("你已经退出");
exit(0);
}
len = strlen(bufSend);
if (len > 997)
len = 997;
bufSend[len] = '\r';
bufSend[len + 1] = '\n';
bufSend[len + 2] = 0;
ret = send(sock, bufSend, strlen(bufSend), 0);
if (ret == SOCKET_ERROR) {
AfxMessageBox("send:%d\n", WSAGetLastError());
break;
}
FD_ZERO(&readSet);
FD_SET(sock, &readSet);
tv.tv_sec = 3;
tv.tv_usec = 0;
ret = select(0, &readSet, NULL, NULL, NULL);
if (ret == SOCKET_ERROR) {
AfxMessageBox("select: %d", WSAGetLastError());
break;
}
if (ret == 0) {
AfxMessageBox("TimeOut,No Response From Server\n");
break;
}
if (FD_ISSET(sock, &readSet)) {
memset(bufRecv, 0, 1000);
ret = recv(sock, bufRecv, 1000, 0);
if (ret == SOCKET_ERROR) {
AfxMessageBox("recv: %d\n", WSAGetLastError());
break;
}
else
{
mfc.m_chatlog.SetSel(-1, -1);
mfc.m_chatlog.ReplaceSel((LPCTSTR)bufRecv);
}
}
if (sock != INVALID_SOCKET)
{
closesocket(sock);
}
}
WSACleanup();
AfxMessageBox("Stopped.\n");
return 0;

}
void ErrorHandling(char* message)
{
AfxMessageBox(message);
exit(1);
}

头文件

stdafx.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

#include <stdio.h>
#include <stdlib.h>
#include <WinSock2.h>
#include <Windows.h>
#pragma comment(lib, "ws2_32.lib")
#include <Ws2tcpip.h>
void ErrorHandling(char* message);
extern SOCKET sock;
#define BUF_SIZE 5000
#include <iostream>
using namespace std;
extern char bufSend[BUF_SIZE];
extern char bufRecv[BUF_SIZE];
extern sockaddr_in sockAddr;
extern int count;

stdafx.cpp

1
2
3
4
5
SOCKET sock;
char bufSend[BUF_SIZE] = { 0 };
char bufRecv[BUF_SIZE] = { 0 };
sockaddr_in sockAddr;
int count=0;

第二章

客户端界面

第二章界面.png
连接按钮对应代码

1
2
3
4
5
char *ip = m_addr.GetBuffer(0);
char *port = m_port.GetBuffer(0);
UpdateData(false);
char *argv[] = { "main.exe",ip,port };
main(3, argv);

主程序代码

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
void ErrorHandling(char* message);

int main(int argc, char *argv[])
{
CTCPsocketDlg mfc;
SOCKET sock;
sockaddr_in sockAddr;
if (argc != 3)
{
//printf("Usage : %s <IP> <port>\n ", argv[0]);
AfxMessageBox("Usage : main.exe <IP> <port>\n ");

exit(1);
}
//初始化DLL
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
ErrorHandling("WSAStartup() error!");
//向服务器发起请求
char message[30];
int strlen = 0;
int idx = 0, readlen = 0;
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET)
{
WSACleanup();
ErrorHandling("socket() error");

}
memset(&sockAddr, 0, sizeof(sockAddr)); //每个字节都用0填充
sockAddr.sin_family = PF_INET;
// sockAddr.sin_addr.s_addr = inet_addr(argv[1]);

InetPton(AF_INET, argv[1], &sockAddr.sin_addr);
sockAddr.sin_port = htons(atoi(argv[2]));
if (connect(sock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR)) == SOCKET_ERROR)
{
/*if (sock != INVALID_SOCKET)
closesocket(sock);
WSACleanup();*/
ErrorHandling("connect() error");
}
/*else
{
//printf("成功连接");
AfxMessageBox("成功连接");
}*/
while (readlen = recv(sock, &message[idx++], 1, 0))
{
if (readlen == -1)
ErrorHandling("read() error!");
strlen+= readlen;
}
mfc.m_mess = message;
mfc.m_read_count = strlen;
closesocket(sock);
WSACleanup();
return 0;

}
void ErrorHandling(char* message)
{
AfxMessageBox(message);
exit(1);
}

35页第6题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
将客户端内的
while (readlen = recv(sock, &message[idx++], 1, 0))
{
if (readlen == -1)
ErrorHandling("read() error!");
strlen+= readlen;
}
改为
for(int i=0;i<3000:i++)
printf("write Busy");
while (readlen = recv(sock, &message[idx++], strlen(message), 0))
{
if (readlen == -1)
ErrorHandling("read() error!");
strlen+= readlen;
}
1
2
3
4
5
6
7
8
将服务器端内的
send(sock,message,strlen(message),0);
改为
if(strlen(message)>0)
for(int i=0;i<strlen(message);i++)
write(sock,message,1,0)
else
printf("write() error!");

总结

我再也不想用mfc工程写GUI界面了。