开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第5天,点击查看活动详情
相关原理
1. 阻塞
在网络通信过程中,像recvfrom()/recv()/accept()等相关的函数,在进行网络相关数据的接收时,会默认阻塞的等待,这种通信方式叫做阻塞IO
2. 非阻塞
非阻塞就是没有接收数据的时候,并没有继续等待,而是报出一个异常,这样程序就会执行到下个流程继续执行,不会影响到后面的操作
3. 阻塞模式下设计思路
服务器,使用ServerSocket监听指定的端口,端口可以随意指定(由于1024以下的端口通常属于保留端口,在一些操作系统中不可以随意使用,所以建议使用大于1024的端口),等待客户连接请求,客户连接后,会话产生;在完成会话后,关闭连接。
客户端:
使用Socket对网络上某一个服务器的某一个端口发出连接请求,一旦连接成功,打开会话;会话完成后,关闭Socket。客户端不需要指定打开的端口,通常临时的、动态的分配一个1024以上的端口。
服务器端:
① 初始化Windows Socket;
② 创建一个监听的Socket;
③ 设置服务器地址信息,并将监听端口绑定到这个地址上;
④ 开始监听;
⑤ 接受客户端连接;
⑥ 和客户端通信;
⑦ 结束服务并清理Windows Socket和相关数据,或者返回第4步。
4.Socket
Windows Socket是从UNIX Socket继承发展而来,最新的版本是2.2。进行Windows网络编程,需要在程序中包含WINSOCK2.H或MSWSOCK.H,同时需要添加引入库WS2_32. LIB或WSOCK32.LIB。
所谓Socket通常也称作”套接字”,用于描述IP地址和端口,是一个通信链的句柄。应用程序通常通过”套接字”向网络发出请求或者应答网络请求。
ServerSocket用于服务器端,Socket是建立网络连接时使用的。 在连接成功时,应用程序两端都会产生一个Socket实例,操作这个实例,完成所需的会话。对于一个网络连接来说,套接字是平等的,并没有差别,不因为在服务器端或在客户端而产生不同级别。不管是Socket还是ServerSocket它们的工作都是通过SocketImpl类及其子类完成的。
Sockets(套接字)编程有三种,流式套接字(SOCK_STREAM),数据报套接字(SOCK_DGRAM),原始套接字(SOCK_RAW) ;
基于TCP的socket编程是采用的流式套接字。
5.多线程运行机制
server服务端监听某个socket端口等待客户端client连接,在客户端连接成功后创建一个线程(pthread_create)来处理该连接,在主线程中监听客户端的连接,在子线程中处理每个socket连接
相关步骤
1.服务器端
//初始化对话框
BOOL CCSocketDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// Add "About..." menu item to system menu.
// IDM_ABOUTBOX must be in the system command range.
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL)
{
CString strAboutMenu;
strAboutMenu.LoadString(IDS_ABOUTBOX);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}
// Set the icon for this dialog. The framework does this automatically
// when the application's main window is not a dialog
SetIcon(m_hIcon, TRUE); // Set big icon
SetIcon(m_hIcon, FALSE); // Set small icon
// TODO: Add extra initialization here
count=0;
m_list.InsertColumn(0,"消息");
m_list.SetColumnWidth(0,435);
m_edit.SetLimitText(99);
for (int i=0;i<50;i++)
msgsock[i]=NULL;
//设定地址
serv.sin_addr.s_addr=htonl(INADDR_ANY);
serv.sin_family=AF_INET;
serv.sin_port=5000;//htons(5000);
addlen=sizeof(serv);
m_button.EnableWindow(FALSE);
//创建socket
sock=socket(AF_INET,SOCK_STREAM,0);
//绑定
if (bind(sock,(sockaddr*)&serv,addlen))
{
m_edit.SetWindowText("绑定错误");
}else
{
//m_list.InsertItem(count++,inet_ntoa(serv.sin_addr));
m_edit.SetWindowText("服务器创建成功");
//开始侦听
listen(sock,5);
//调用线程
AfxBeginThread(&thread,0);
}
return TRUE; // return TRUE unless you set the focus to a control
}
//服务器线程
UINT thread(LPVOID p)
{
char buff[100];
CSize size;
size.cx=0;
size.cy=30;
int s=1,msgcount,loop=1,flag=0;
CCSocketDlg *dlg=(CCSocketDlg*)AfxGetApp()->GetMainWnd();
//获得客户端数量
msgcount=dlg->getcount();
if (msgcount==-1)
loop=0;
if(loop)
{
s=1;
dlg->msgsock[msgcount]=accept(dlg->sock,(sockaddr*)&(dlg->serv),&(dlg->addlen));
if (dlg->msgsock[msgcount]==INVALID_SOCKET)
{
dlg->m_edit.SetWindowText("Error accept");
}
else
{
//启动线程
AfxBeginThread(thread,0);
dlg->SetForegroundWindow();
dlg->m_list.InsertItem(dlg->count++,"连接成功");
dlg->m_list.InsertItem(dlg->count++,inet_ntoa(dlg->serv.sin_addr));
dlg->m_list.Scroll(size);
dlg->m_button.EnableWindow(TRUE);
while(s!=SOCKET_ERROR)
{
//循环接收数据
s=recv(dlg->msgsock[msgcount],buff,100,0);
dlg->SetForegroundWindow();
if (s!=SOCKET_ERROR)
{
dlg->m_list.InsertItem(dlg->count++,buff);
dlg->m_list.Scroll(size);
dlg->sendtoall(dlg->msgsock[msgcount],buff);
}
}
send(dlg->msgsock[msgcount],"Disconnected",100,0);
dlg->m_list.InsertItem(dlg->count++,"Disconnected");
dlg->m_list.Scroll(size);
dlg->msgsock[msgcount]=NULL;
for (int i=0;i<50;i++)
if (dlg->msgsock[i]!=NULL)
flag=1;
if (flag!=1)
dlg->m_button.EnableWindow(FALSE);
closesocket(dlg->msgsock[msgcount]);
}
}
//终止线程
AfxEndThread(0);
return 0;
}
//发送数据
void CCSocketDlg::OnButton1()
{
char buff[100];
m_edit.GetWindowText(buff,99);
m_edit.SetWindowText("");
m_list.InsertItem(count++,buff);
CSize size;
size.cx=0;
size.cy=30;
m_list.Scroll(size);
//循环向所有客户发送信息
for (int i=0;i<50;i++)
{
if (msgsock[i]!=NULL)
send(msgsock[i],buff,100,0);
}
}
CCSocketDlg::~CCSocketDlg()
{
for (int i=0;i<50;i++)
if (msgsock[i]!=NULL)
send(msgsock[i],"Disconnected",100,0);
}
//获得还没有使用的socket数组号
int CCSocketDlg::getcount()
{
for (int i=0;i<50;i++)
{
if (msgsock[i]==NULL)
return i;
}
return -1;
}
//向所有客户发送数据
void CCSocketDlg::sendtoall(SOCKET s,char *buff)
{
for (int i=0;i<50;i++)
{
if (msgsock[i]!=NULL && msgsock[i]!=s)
//发送
send(msgsock[i],buff,100,0);
}
}
2.客户端
//发送信息
void CCSocketcliDlg::OnButton1()
{
char buff[100];
CSize size;
size.cx=0;
size.cy=30;
//获得发送信息
m_edit.GetWindowText(buff,99);
m_edit.SetWindowText("");
m_list.InsertItem(count++,buff);
m_list.Scroll(size);
//发送数据
send(clisock,buff,100,0);
}
//线程
UINT thread(LPVOID v)
{
char buff[100];
char array[25][30]=
{"155.245.160.151",
"155.245.160.152",
"155.245.160.153",
"155.245.160.154",
"155.245.160.155",
"155.245.160.156",
"155.245.160.157",
"155.245.160.158",
"155.245.160.159",
"155.245.160.160",
"155.245.160.161",
"155.245.160.162",
"155.245.160.163",
"155.245.160.164",
"155.245.160.165",
"155.245.160.166",
"155.245.160.167",
"155.245.160.168",
"155.245.160.169",
"155.245.160.170",
"155.245.160.171",
"155.245.160.172",
"155.245.160.173",
"155.245.160.174",
"155.245.160.175"};
CSize size;
size.cx=0;
size.cy=30;
int s=1,addcount=0;
CCSocketcliDlg *dlg=(CCSocketcliDlg*) AfxGetApp()->GetMainWnd();
dlg->m_connect.EnableWindow(FALSE);
dlg->m_disconnect.EnableWindow(TRUE);
//连接到服务器
while(connect(dlg->clisock,(sockaddr*)&(dlg->cli),sizeof(dlg->cli)) && dlg->ee!=0)
{
dlg->m_edit.SetWindowText("等待.....");
//空循环
for (int i=0;i<=65000;i++)
for(int j=0;j<=200;j++);
if (addcount==25)
addcount=0;
dlg->cli.sin_addr.s_addr=inet_addr(array[addcount++]);
}
if (dlg->ee==1)
dlg->m_list.InsertItem(dlg->count++,"连接成功");
dlg->m_button1.EnableWindow(TRUE);
dlg->SetForegroundWindow();
//循环获得数据
while(s!=SOCKET_ERROR && dlg->ee!=0)
{
//调用recv函数接收数据
s=recv(dlg->clisock,buff,100,0);
dlg->SetForegroundWindow();
if (s!=SOCKET_ERROR && dlg->ee!=0)
dlg->m_list.InsertItem(dlg->count++,buff);
dlg->m_list.Scroll(size);
}
//发送断开命令
send(dlg->clisock,"Disconnected",100,0);
dlg->m_button1.EnableWindow(FALSE);
dlg->m_connect.EnableWindow(TRUE);
dlg->m_disconnect.EnableWindow(FALSE);
closesocket(dlg->clisock);
AfxEndThread(0);
return 0;
}
CCSocketcliDlg::~CCSocketcliDlg()
{
send(clisock,"Disconnected",100,0);
}
//连接服务器
void CCSocketcliDlg::OnButton2()
{
char ipaddress[35];
m_edit2.GetWindowText(ipaddress,30);
cli.sin_addr.s_addr=inet_addr(ipaddress);
cli.sin_family=AF_INET;
cli.sin_port=5000;//htons(5000);
//创建socket
clisock=socket(AF_INET,SOCK_STREAM,0);
ee=1;
//启动线程
AfxBeginThread(thread,0);
}
运行结果
输入本机ip地址后点击链接即可运行获得运行结果
当前服务端已创建的线程数量为2.
运行程序后结果如图1所示,客户端显示“服务器创建成功”语句,通过发送消息,客户端和服务器端消息同步,效果如图。
断开客户端连接会输出语句如图三所示,跳出:DIsconnected提示。