socket 双机通信 返回主页
Demo下载
任务描述
利用 Socket 来实现双机通信,理解 TCP 状态机图 实验内容:使用 Socket 编程,采用其中的 TCP 面向连接方式,实现计算机数 据的交换。
实验软件和硬件环境:
实验软件:
VS2019, MFC 类库
实验硬件:
Intel® Core™ i5-8250U CPU @1.60GHz 1.80GHz
技术原理:
- 利用 socket 实现通信
- 利用多线程实现异步通信
测试结果:
关键代码:
服务端:
(输入端口后) 点击开启按钮: 客户端再次连接通信:
void CServerDlg::OnBnClickedButton1()
{
// TODO: 在此添加控件通知处理程序代码
UpdateData(TRUE);
int myport = _ttoi(s_port);
UpdateData(FALSE);
if (myport < 1024 || myport > 65535) {
UpdateData(FALSE);
MessageBox(TEXT("输入的端口不合适"), TEXT("提示"));
return;
}
EndFlag = 0;
GetDlgItem(IDC_BUTTON1)->ShowWindow(SW_HIDE);
GetDlgItem(IDC_BUTTON2)->ShowWindow(SW_SHOW);
GetDlgItem(IDC_BUTTON3)->ShowWindow(SW_SHOW);
WSADATA wsData;
WSAStartup(MAKEWORD(2, 2), &wsData);
CString IP= _T("ip:0.0.0.0");
AfxBeginThread(&StartFunc, 0); //开启线程
WSACleanup();
}
线程函数 StartFunc():
UINT StartFunc(LPVOID p) {
WSADATA wsaData;
WORD wVersion;
wVersion = MAKEWORD(2, 2);
WSAStartup(wVersion, &wsaData);
SOCKADDR_IN local_addr;
SOCKADDR_IN client_addr;
CString port;
CServerDlg* dlg = (CServerDlg*)AfxGetApp()->GetMainWnd(); //得到主窗口指针
dlg->GetDlgItemTextW(IDC_EDIT1, port);
//创建socket
local_addr.sin_family = AF_INET;
local_addr.sin_port = htons(_ttoi(port));
local_addr.sin_addr.s_addr = htonl(INADDR_ANY);
if ((serSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET)
{
dlg->update( _T("创建失败"));
return 0;
}
BOOL debug = true; //端口复用
setsockopt(serSocket, SOL_SOCKET, SO_REUSEADDR, (const char*)&debug, sizeof(BOOL));
//绑定
bind(serSocket, (struct sockaddr*) & local_addr, sizeof(SOCKADDR_IN));
//监听
listen(serSocket, 2);
dlg->update( _T("已成功开启...."));
while (1) {
AfxBeginThread(&AcceptFunc, 0);
if (EndFlag) break; //EndFlag表示已经点击关闭按钮,需要停止线程
}
closesocket(cliSocket);
return 0;
}
线程函数 AcceptFunc():
UINT AcceptFunc(LPVOID p) {
CString port;
CServerDlg* dlg = (CServerDlg*)AfxGetApp()->GetMainWnd(); //得到主窗口指针
SOCKADDR_IN client_addr;
int iaddrSize = sizeof(SOCKADDR_IN);
int res;
char msg[256] = { 0 };
if ((cliSocket = accept(serSocket, (struct sockaddr*) & client_addr, &iaddrSize)) ==
INVALID_SOCKET)
{
dlg->update(_T("连接失败"));
return 0;
}
while (1) //接收信息
{
if (EndFlag) {
return 0;
}
if ((res = recv(cliSocket, msg, 256, 0)) <=0)
{
dlg->update(_T("失去连接")); return 0;
}
else
{
USES_CONVERSION;
CString strRecv = A2T(msg);
dlg->update( _T("客户端>>") + strRecv);
}
}
return 0;
}
客户端: (输入 IP 地址,端口后) 客户端点击连接按钮:
void CClientDlg::OnBnClickedButton1()
{
// TODO: 在此添加控件通知处理程序代码
UpdateData(TRUE);
int myport = _ttoi(sPort);
if (myport < 1024 || sPort.IsEmpty()) {
UpdateData(FALSE);
MessageBox(TEXT("输入的端口不合适"), TEXT("提示"));
return;
}
ClearAll(); //清屏
UpdateData(FALSE);
WSADATA wsaData;
SOCKADDR_IN server_addr;
WORD wVersion;
wVersion = MAKEWORD(2, 2);
WSAStartup(wVersion, &wsaData);
server_addr.sin_addr.s_addr = htonl(sIP);
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(_ttoi(sPort));
//创建socket
if ((cliSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET)
{
update(_T("创建Soket错误!"));
return;
}
//socket连接
if (connect(cliSocket, (struct sockaddr*) & server_addr, sizeof(SOCKADDR_IN)) == SOCKET_ERROR)
{
update(_T("连接失败"));
return;
}
else
{
update(_T("连接成功"));
GetDlgItem(IDC_BUTTON1)->ShowWindow(SW_HIDE);
GetDlgItem(IDC_BUTTON2)->ShowWindow(SW_SHOW);
GetDlgItem(IDC_BUTTON3)->ShowWindow(SW_SHOW);
EndFlag = 0;
AfxBeginThread(&RecvFunc, 0); //开启线程
}
}
线程函数 RecvFunc():
UINT RecvFunc(LPVOID p) {
int res;
char msg[256] = { 0 };
CString s;
CClientDlg* dlg = (CClientDlg *)AfxGetApp()->GetMainWnd(); //得到主窗口指针
dlg->update(_T("开始聊天"));
while (1) //接收数据
{
if (EndFlag) {
dlg->ClearAll();
EndFlag = 0;
break;
}
if ((res = recv(cliSocket, msg, 256, 0)) == -1)
{
dlg->update(_T("失去连接"));
break;
}
else
{
USES_CONVERSION;
CString strRecv = A2T(msg);
dlg->update(_T("服务器>>") + strRecv);
}
}
return 0;
}
服务端与客户端近似的功能函数: 点击发送按钮:
void CServerDlg::OnBnClickedButton3()
{
// TODO: 在此添加控件通知处理程序代码
UpdateData(TRUE);
char msg[256] = { 0 };
USES_CONVERSION;
strcpy_s(msg, 256, T2A(s_send));
UpdateData(FALSE);
if (s_send.GetLength() == 0)
{
MessageBox(_T("请输入信息"));
}
else if (send(cliSocket, msg, 256, 0) == SOCKET_ERROR) //发送数据
{
update( _T("发送失败"));
}
else
{
update( _T("服务器>>") + s_send);
}
UpdateData(TRUE);
s_send = _T("");
UpdateData(FALSE);
}
点击关闭或者断开按钮:
void CServerDlg::OnBnClickedButton2()
{
// TODO: 在此添加控件通知处理程序代码
UpdateData(TRUE);
s_page = _T("");
UpdateData(FALSE);
EndFlag = 1;
GetDlgItem(IDC_BUTTON1)->ShowWindow(SW_SHOW);
GetDlgItem(IDC_BUTTON2)->ShowWindow(SW_HIDE);
GetDlgItem(IDC_BUTTON3)->ShowWindow(SW_HIDE);
}
获取时间函数:
CString getNowtime() {
CTime tm; tm = CTime::GetCurrentTime();
CString tim;
tim = tm.Format("%Y年%m月%d日 %X");
return tim;
}
将消息更新到全局消息框中:
void CServerDlg::update(CString s)
{
UpdateData(TRUE);
s_page += getNowtime() + _T("\r\n") + s +_T("\r\n");
UpdateData(FALSE);
}
实验过程中遇到的问题及解决处理方法
- 服务端点击关闭按钮后,再次点击启动发现不能使用同一端口:
setsockopt()函数实现端口复用
BOOL m=TRUE; setsockopt() (serSocket, SOL_SOCKET, SO_REUSEADDR, (const char*)&(m), sizeof(BOOL)); - 实现异步通信
服务端点击启动按钮响应函数内调用 StartFunc()线程:
StartFunc(){bind();listen();accept(); while(1){ recv();} close();return 0;}客户端点击连接按钮响应函数内调用 RecvFunc()线程:
RecvFunc(){while(1){ recv();} return 0;} - 客户端不能多次连接,再一次就连不上:
服务端中,把 Accept()和 Recv()放到一个线程中 AcceptFunc();在 StartFunc()线程中循环调用线
程 AcceptFunc()。
StartFunc(){bind();listen(); while(1){ AcceptFunc();} close();return 0;} AcceptFunc(){ accept(); while(1){ recv();} return 0;}总结:
- 对 socket 编程有了更深的理解,socket 利用三元组[IP,协议,端口]进行网络间通 信,服务端调用 listen()进入监听状态,通过 accept()来接收客户端请求;客户端 调用 connect()函数请求连接。双方通过调用 recv()和 send()来接收和发送数据。
- 对于同步和异步通信的概念和应用有了更深的理解。实现双向异步通信,可以利用 多线程实现,通过线程来使得接收信息和发送信息功能不相关联,不必接收一步发 送一步。