服务器界面:
大家先去 附件中 把代码下下来,否则看文章是看不懂的。服务端代码,打开类视图,找到ListenThreadFunc函数。这是我们的线程函数,socket代码在该线程里执行。
01 |
DWORD WINAPI
ListenThreadFunc( LPVOID Lparam) |
03 |
Cxads_PCServerDlg
* pServer = (Cxads_PCServerDlg *)Lparam; |
04 |
if (INVALID_SOCKET
== (pServer->m_SockListen = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))) |
06 |
AfxMessageBox(_T( "建立socket失败" )); |
10 |
service.sin_family
= AF_INET; |
11 |
service.sin_addr.s_addr
= htonl(INADDR_ANY); |
12 |
service.sin_port
= htons(pServer->m_ServicePort); |
13 |
if (0
!= bind(pServer->m_SockListen,(sockaddr *)&service, sizeof (sockaddr_in))) |
15 |
AfxMessageBox(_T( "绑定端口失败" )); |
18 |
if (0
!= listen(pServer->m_SockListen,5)) |
20 |
AfxMessageBox(_T( "监听端口失败" )); |
25 |
pServer->EnableWindow(IDC_BUTTONEND,TRUE); |
26 |
//
pServer->EnableWindow(IDC_BUTTONSEND,TRUE); |
27 |
pServer->EnableWindow(IDC_BUTTONSTART,FALSE); |
28 |
pServer->SetRevBoxText(_T( "服务启动成功,开始监听端口" )); |
32 |
if (socket_Select(pServer->m_SockListen,100,TRUE)) |
34 |
sockaddr_in
clientAddr; |
35 |
int iLen
= sizeof (sockaddr_in); |
36 |
SOCKET
accSock = accept(pServer->m_SockListen,( struct sockaddr
*)&clientAddr,&iLen); |
37 |
if (accSock
== INVALID_SOCKET) |
43 |
tItem.m_ClientSocket
= accSock; |
44 |
tItem.m_strIp
= inet_ntoa(clientAddr.sin_addr); //IP地址 |
45 |
tItem.m_pMainWnd
= pServer; |
46 |
int idx
= pServer->m_ClientArray.Add(tItem); //idx是第x个连接的客户端 |
47 |
tItem.m_hThread
= CreateThread(NULL,0,ClientThreadProc, //创建一个线程并挂起:CREATE_SUSPENDED |
48 |
&(pServer->m_ClientArray.GetAt(idx)),CREATE_SUSPENDED,NULL); |
49 |
pServer->m_ClientArray.GetAt(idx).m_hThread
= tItem.m_hThread; |
50 |
//等把hThread加入了节点,才开始执行线程,如下 |
51 |
ResumeThread(tItem.m_hThread); |
52 |
pServer->SetRevBoxText(tItem.m_strIp
+ _T( "上线" )); |
Cxads_PCServerDlg是我们对话框程序的类,我们传进来的参数就是这个类对象。pServer指向该对象。
1.pServer->m_SockListen = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))
socket函数初始化一个socket。socket是什么?socket相当于通信的一个管道,m_SockListen是一个监听的socket。我们一会就要监听这根"管道",看是否有客户端连接。socket()中的参数像我这样写,代表这是个TCP连接。TCP连接和UDP连接的区别就是,TCP是要双方建立连接后才能通信,就想打电话;而UDP是单方面就能发送信息,就想发短信。
2.bind(pServer->m_SockListen,(sockaddr *)&service,sizeof(sockaddr_in))
bind函数是绑定一个端口和IP地址。第一个参数是刚才新建的监听socket,第二个参数是一个sockaddr_in结构体,里面保存着IP地址和端口。因为我们这是服务端,所以保存的IP地址和端口是自己的,htonl(INADDR_ANY)就是将IP地址绑定为任意,这样你的IP可以是127.0.0.1,也可以是192.168.x.x,也可以是你的外网IP。htons(pServer->m_ServicePort)是要监听的IP,我这里是从输入框获取的,你也可以使用固定的比如htons(8260)。
3.listen(pServer->m_SockListen,5)
开始监听了。5代表等待连接队伍的最大长度,一般都是5就行。
4.
进入while循环,大家可以看到这是个死循环。while(TRUE)永远不会退出去。while中间只有个if语句,socket_Select(pServer->m_SockListen,100,TRUE)是一个select选择模型,作用是向socket中看一眼,如果有信息则返回TRUE,如果没有信息则返回FALSE。有信息代表有客户端来连接了,于是我们进入if语句。
5.SOCKET accSock = accept(pServer->m_SockListen,(struct sockaddr *)&clientAddr,&iLen);
大家可以看到我又建了一个socket,不是刚才那个了。accept函数就返回一个socket,这个socket就是和该客户端通信的"管道"。传入的参数和bind类似,只是第二个变成得到客户端的IP与端口了。
6.
之后有一个将节点加入链表的过程。因为我们连接服务端的客户端不止一个,所以我们要将每一个客户端的IP、SOCKET和相关信息加入链表,以供以后使用。而且大家可以看到,我们这个while里面又新开了一个线程,这个线程的作用就是和该客户端通信。
7.
休息100毫秒后while循环继续执行,等待下一个客户端连接。
这就是服务端socket的代码,通信的部分我客户端里介绍,服务端和客户端基本是一样的,大家可以自己看代码比较。
打开客户端,找到ConnectSocket方法,这是我们从socket中获取信息的一个方法。
01 |
BOOL Cxads_PCClientDlg::ConnectSocket(Cxads_PCClientDlg
* pClient) |
03 |
m_ClientSock
= socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); |
04 |
if (NULL
== m_ClientSock) |
06 |
MessageBox(_T( "创建socket失败" )); |
10 |
sa.sin_family
= AF_INET; |
12 |
DWORD dPort
= GetDlgItemInt(IDC_EDITPORT); |
13 |
GetDlgItemText(IDC_IPADDRESS,strIp); |
14 |
if (strIp
== _T( "0.0.0.0" )
|| (dPort >= 65535 && dPort < 1024) || dPort == 0) |
16 |
MessageBox(_T( "请输入正确IP地址或端口" )); |
19 |
sa.sin_port
= htons(dPort); |
21 |
USES_CONVERSION; //定义后才能使用T2A |
22 |
sprintf_s(szIpAdd,32, "%s" ,T2A(strIp)); |
23 |
sa.sin_addr.S_un.S_addr
= inet_addr(szIpAdd); |
24 |
if (SOCKET_ERROR
== connect(m_ClientSock,(sockaddr *)&sa, sizeof (sa))) |
26 |
MessageBox(_T( "连接客户端错误,请检查你填写的IP和端口是否错误" )); |
30 |
pClient->SetRevBoxText(_T( "连接服务器成功" )); |
31 |
pClient->EnableWindow(IDC_BUTTONSTOP,TRUE); |
32 |
pClient->EnableWindow(IDC_BUTTONCONNECT,FALSE); |
34 |
OnEnChangeEditsendbox(); |
39 |
if (socket_Select(m_ClientSock,100,TRUE)) |
41 |
char szMsg[MAX_BUFF]
= {0}; |
42 |
int iRead
= recv(m_ClientSock,szMsg,MAX_BUFF,0); |
46 |
pClient->SetRevBoxText(strIp
+ _T( ">>" )
+ strMsg); |
50 |
pClient->SetRevBoxText(_T( "已断线,请重新连接" )); |
1.
同服务端一样,调用socket()初始化socket.
2.connect(m_ClientSock,(sockaddr *)&sa,sizeof(sa))
连接服务端。和服务端的accept函数类似,第二个参数传入sockaddr_in结构体,里面保存着服务端的IP地址和端口。如果连接成功,则返回0。
3.
进入一个while循环。这个while虽然也是while(TRUE),但不是一个死循环,中间有break可以使while循环退出。while中仍然用到socket_Select()函数,作用也是向socket中看一眼,如果有信息则返回T,否则返回F。有信息代表从服务端发送来了内容。
4.int iRead = recv(m_ClientSock,szMsg,MAX_BUFF,0);
recv()函数接受信息,第一个参数传入socket,第二个参数传入接受的信息保存的字符串数组,第三个传入数组长度,第四个填0即可。返回值是接受信息的字节数,如果=0说明对方不在线了。
这个方法告一段落。有人问了,那怎么发送信息呢?别急,在类视图中找到OnBnClickedButtonsend()方法。
01 |
void Cxads_PCClientDlg::OnBnClickedButtonsend() |
03 |
//
TODO: 在此添加控件通知处理程序代码 |
05 |
char szBuf[256]
= {0}; |
08 |
GetDlgItemText(IDC_EDITSENDBOX,strGetMsg); |
09 |
strcpy_s(szBuf,T2A(strGetMsg)); |
10 |
iWrite
= send(m_ClientSock,szBuf,256,0); |
11 |
if (SOCKET_ERROR
== iWrite){ |
12 |
SetRevBoxText(_T( "发送错误" )); |
14 |
SetRevBoxText(_T( "我自己
>>" )
+ strGetMsg); |
15 |
SetDlgItemText(IDC_EDITSENDBOX,_T( "" )); |
这是点击了发送按钮执行的代码。只有一个点,就是send()函数。
iWrite = send(m_ClientSock,szBuf,256,0);
与recv类似,发送保存在szBuf中的内容到m_ClientSock。
时间: 2024-11-06 13:13:50