【网络程序设计】TCP实现C/S模式的聊天程序

347 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 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所示,客户端显示“服务器创建成功”语句,通过发送消息,客户端和服务器端消息同步,效果如图。

image.png

image.png

断开客户端连接会输出语句如图三所示,跳出:DIsconnected提示。

image.png