SOCKET控制台双线程聊天程序
今天算是终于拿下了最简单的socket(tcp模式)+线程的聊天程序,嘿嘿,还记得前些天写这个程序的狼狈情景,这下总算能扬眉吐气了哈。
对于一般的socket编程入门例子,不外乎讲下如何用套接字发送一个信息,最终得到的应用程序和我们的QQ这种聊天程序比起来,实在是不能算东西。看孙鑫的视频时,我就想写个自己的聊天程序,功能是能够聊天和发送文件。
在后来实现时发现要想在发送信息的同时,能够接收信息,就需要在主线程外有另一个线程帮忙,乖乖,只好再去学会线程的知识。
经过几天的学习(其实每天还要上课,玩游戏…),咱家也算是有了点心得,一口气就搞定了这个简单的聊天程序。功能:只能聊天,且是单聊,断线需重启,哈哈。
稍微有点特色的功能是对字符串的处理,使聊天看起来有几分界面的效果。
好,不多说,来说下程序的具体实现。
客户端代码如下:
#pragma comment(lib,"ws2_32.lib")
#include<winsock2.h>
#include<iostream>
#include<string>
using namespace std;
int follow=0;
string show="";
void recvProc(SOCKET sockConnect)
{
char msgRcv[100]={0};
while(true)
{
if(SOCKET_ERROR==recv(sockConnect,msgRcv,sizeof(msgRcv),0))
{
cout<<"/n对方已经下线了";
return;
}
if(msgRcv[0]!='/0')
{
show.erase(show.end()-7,show.end());
show+="对方说: ";
show+=msgRcv;
show+='/n';
show+="input: ";
system("cls");
cout<<show;
}
}
}
int main(int argc, char* argv[])
{
//Load socket and its version
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 1, 1 );//MAKEWORD macro
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
return 1;
}
if ( LOBYTE( wsaData.wVersion ) != 1||
HIBYTE( wsaData.wVersion ) != 1) {
WSACleanup( );//low(main) and high(deputy)
return 1;
}
//create a socket and set it
SOCKET sockSrv=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
sockaddr_in addrSrv;
memset(&addrSrv,0,sizeof(addrSrv));
addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(6000);
//bind
if(bind(sockSrv,(sockaddr*)&addrSrv,sizeof(sockaddr))==SOCKET_ERROR)
{
cout<<"bind error"<<endl;
}
//listen
if(listen(sockSrv,5)==SOCKET_ERROR)
{
cout<<"listen error"<<endl;
}
//accept
SOCKADDR_IN addrClient;
int len=sizeof(sockaddr);//caution:len should be initial as sizeof(sockaddr),
//or accepting will be failed
while(true)
{
cout<<"等待客户建立连接...";
SOCKET sockConnect=accept(sockSrv,(sockaddr*)&addrClient,&len);
if(sockConnect==INVALID_SOCKET)
{
cout<<"invalid socket"<<endl;
return 0;
}
//recv Thread
CreateThread(NULL,0,
(LPTHREAD_START_ROUTINE)recvProc,(void*)sockConnect,
0,NULL);
//send
while(true)
{
char buf[100]={0};
show+="input: ";
system("cls");
cout<<show;
cin>>buf;
show.erase(show.end()-7,show.end());
show+="我说:";
show+=buf;
show+='/n';
send(sockConnect,buf,sizeof(buf),0);
}
closesocket(sockConnect);
}
//end winsock dll
WSACleanup();
return 0;
}
如果你已经开始学socket编程,和我差不多水平,就可以直接跳到
while(true)
{
cout<<"等待客户建立连接...";
……
处,前面我不说什么,说接下来的。
Accept函数返回一个新的SOCKET后,进程开始通信了,由于需要将发送与接收2个操作分离开来,且应经有了一个main主线程,因此我们只需再创建一个线程就可以了,这个线程可以是send,也可以是recv,我这里选的是recv,主线程负责发送信息。
注意这个recvProc的参数(唯一一个)是SOCKET,而这个SOCKET就是刚刚Accept函数返回的哈,这样就建立了与主线程的关系。(这个SOCKET像一个句柄,提供访问)
RecvProc干自己的事,接收信息。由于是阻塞式模式,因此即使是while循环,也不会耗费CPU资源,阻塞式模式下recv会等到有信息来到才允许往下运行,否则就挡在那,挂着。
主线程中的while循环也不会浪费CPU资源,cin是和recv一样的家伙,等待着使用者输入。
那么运行的情况(界面)是怎么样的呢?
可以这样来理解,主线程和子线程共同使用I/O设备输出信息,大家各自输出自己的东西,各不干涉。但是这样容易出现紊乱,一下主线程输出,一下子线程输出,主线程还要输入。防止紊乱发生的方法想了几个,但都不大理想,最后想到用一个标准库库的string对象来保存界面上所有的信息,嘿嘿,这下就好看了。大家可以看下string对象show的变化,有2处地方,一是recv处,一是send处,有点绕的,呵呵。
至于客户端,只在最后发下代码,道理都一样。
有一个问题就是string对象show太大了怎么办,我想如果show的’/n’的个数达到控制台界面的行数时,如果再增加字符串,就将多余的部分保存到文件中,然后从show中删除,就可以了。
当然,程序还有许多地方有问题,如没有释放子线程的资源,等等,呵呵…
附:客户端源代码:
#pragma comment(lib,"ws2_32.lib")
#include<winsock2.h>
#include<iostream>
#include<string>
using namespace std;
//CLIENT CPP
string show="";
void recvProc(SOCKET sockClient)
{
char msgRcv[100]={0};
while(true)
{
if(SOCKET_ERROR==recv(sockClient,msgRcv,sizeof(msgRcv),0))
{
cout<<"/n对方已经下线了";
return;
}
if(msgRcv[0]!='/0')
{
show.erase(show.end()-7,show.end());
show+="对方说: ";
show+=msgRcv;
show+='/n';
show+="input: ";
system("cls");
cout<<show;
}
}
}
int main(int argc, char* argv[])
{
//Load socket and its version
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 1, 1 );//MAKEWORD macro
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
return 1;
}
if ( LOBYTE( wsaData.wVersion ) != 1||
HIBYTE( wsaData.wVersion ) != 1) {
WSACleanup( );//low(main) and high(deputy)
return 1;
}
//create a client socket
SOCKET sockClient=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
//set the address of the server
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr=inet_addr("192.168.1.100");
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(6000);
//connect
connect(sockClient,(sockaddr*)&addrSrv,sizeof(sockaddr));
//recieve msg Thread
CreateThread(NULL,0,
(LPTHREAD_START_ROUTINE)recvProc,(void*)sockClient,
0,NULL);
//send msg
while(true)
{
char buf[100]={0};
show+="input: ";
system("cls");
cout<<show;
cin>>buf;
show.erase(show.end()-7,show.end());
show+="我说:";
show+=buf;
show+='/n';
send(sockClient,buf,sizeof(buf),0);
}
//close socket to release the resource
closesocket(sockClient);
WSACleanup();
return 0;
}