扫一扫
分享文章到微信
扫一扫
关注官方公众号
至顶头条
作者:张勤 李华春 来源:天极论坛 2007年10月22日
关键字:
图3 第二客户建立连接后的TCP服务器
图4 第二客户建立连接后的数据结构
服务器在指定的端口上监听,每当一个套接口接收到信息,都将会把接收到的信息发送给每一个Client 。其主要源程序如下:
int main(int argc,char **argv)
{
int i,maxi,maxfd,listenfd,connfd,sockfd;
int nready,client;
ssize_t n;
fd_set rset,allset;
char line[MAXLNE];
socklen_t clilen;
struct sockaddr_in cliaddr,servaddr;
const int on=1;
listenfd=Socket(AF_INET,SOCK_STREAM,0);
if(setsockopt (listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))=-1); err_ret(″setsockopt error″);
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family =AF_INET;
servaddr.sin_addr.s_addr =htonl(INADDR_ANY);
servaddr.sin_port =htons(SERV_PORT);
Bind(listenfd,(SA*)&servaddr,sizeof(servaddr));
Listen(listenfd,LISTENQ);
maxfd =listenfd; //初始化
maxi = -1; //最大下标
for(i=0;i<FD_SETSIZE;i++)
client[i]=-1; //-1表示可用
FD_ZERO(&allset);
FD_SET(listenfd,&allset);
for(;;){
rset=allset; //结构赋值
nready=Select(maxfd+1,&rset,NULL,NULL,NULL);
if (FD_ISSET(listenfd,&rset)){ //有用户连接
clilen=sizeof(cliaddr);
connfd=Accept(listenfd,(SA*)&cliaddr,&clilen);
for(i=0;i<FD_SETSIZE;i++)
if(client[i]< 0 {
client[i]=connfd;//保存套接字
break;
}
if(i=FD_SETSIZE);
err_quit(″too many clients″);
FD_SET (connfd,&allset); //增加套接字到读描述字集
if(connfd>maxfd);
maxfd=connfd;
if(i>maxi);
maxi=i;
if(--nready<=0)
continue;
}
for (i=0;i<=maxi;i++){ //检查所有用户连接
if((sockfd=client[i])<0)
continue;
if(FD_ISSET(sockfd,&rset))
{
if((n=Readline(sockfd,line,MAXLINE))=0)
{ //用户关闭连接
Close(sockfd);
FD_CLR(sockfd,&allset);
client[i]=-1
}else
printf(″%s″,line);
Broadcast(client,maxi,line,n);
if (--nready<=0)
break;
}
}
}
}
//将聊天内容发送到所有已连接的用户
Broadcast(int client,int maxi,char*str,size_t n)
{
int i;
int sockfd;
for(i=0;i<maxi,i++){
if((sockfd=client[i])<0)
continue;
writen(sockfd,str,n);
}
}
2.客户端
为了实现非阻塞通信,利用异步选择函数 WSAAsynSelect() 将网络事件与 WinSock 消息联系起来,由该函数注册一些用户感兴趣的网络事件(如接收缓冲区满,允许发送数据,请求连接等)。当这些被注册的网络事件发生时,应用程序的相应函数将接收到有关消息。
应用程序在使用Windows Sockets DLL 之前必须先调用启动函数WSAStartup() ,该函数的功能有两点:一是由应用程序指定所要求Windows Sockets DLL 版本;二是获得系统 Windows Sockets DLL的一些技术细节。每一个WSAStartup()函数必须和一个WSACleanup()函数对应,当应用程序终止时,必须调用 WSACleanup()将自己从OLL 中注销。
客户端程序用 VC++6.0在Windows98 操作系统下设计,程序主框架由 AppWizard 生成,客户端核心代码在 CTalkDialog类中。只有一个 socket 变量m_hSocket 与服务端进行连接。连接建立好后,通过此 SOCKET发送和接收信息。
手工加入CTalkDialog::OnSockConnect() ,完成基本套接字编程,为客户程序申请一个套接字,并将该套接字与指定服务器绑定,然后向服务器发出连接请求,启动异步选择函数等待服务器的响应。
void CTalkDialog:″OnSockConnect()
{
struct sockaddr_in servaddr;
WSADATA wsaData;
if(WSAStartup(WINSOCK_VERSION,&wsaData));
{
MessageBox(″Could not load Windows Sockets DLL,″,NULL,MB_Ok);
return;
}
m_hSocket=socket(AF_INET,SOCK_STREAM,0);
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family=AF_INET;
//m_iPort,m_csIP 为通过注册对话框返回的端口号和IP地址
servaddr.sin_port=htons(m_iport);
servaddr.sin_addr.S_un.S_addr=inet_addr(m_csIP);
if (connect(m_hSocket,(SA*)& servaddr,sizeof (servaddr))!=0){
AfxMessageBox(″连接服务器失败!″);
GetDlgItem(IDC_BUTTON_OUT)->EnableWindow(FALSE);
GetDlgItem(IDC_BUTTON_IN)->EnableWindowTRUE
GetDlgItem(IDC_EDIT-SEND)->EnableWindow(FALSE);
UpdateData(FALSE);
}
else{
GetDlgItem(IDC_BUTTON_IN)->EnableWindow(FALSE);
GetDlgItem(IDC_BUTTON_OUT)->EnableWindow(TRUE);
GetDlgItem(IDC_EDIT-SEND)->EnableWindow(TRUE);
int iErrorCode=WSAAsyncSelect(m_hSocket,m_hWnd,WM_SOCKET_READ, FD_READ);
if(iErrorCode=SOCKET_ERROR)
MessageBox(″WSAAsyncSelect failed on socket″,NULL,MB_OK);
}
} 手工加入CTalkDialog::OnSockRead(), 响应WinSock 发来的消息
LRESULT CTalkDialog::OnSockRead(WPARAM wParamLPARAM 1Param)
{
int iRead;
int iBufferLength;
int iEnd;
int iSpaceRemain;
char chIncomingData[100];
iBufferLength=iSpaceRemain=sizeof (chIncomingData);
iEnd=0;
iSpaceRemain-=iEnd;
iRead=recv(m_hSocket,(LPSTR)(chIncomingData+iEnd),iSpaceRemain,0);
iEnd+=iRead;
if (iRead=SOCKET_ERROR)
AfxMessageBox(″接收数据错误!″);
chIncomingData[iEnd]=‘\0’;
if(lstrlen(chIncomingData)!=0)
{
m_csRecv=m_csRecv+chIncomingData;
GetDlgItem(IDC_EDIT_RECV)->SetWindowText((LPCSTR)m_csRecv);
CEdit*pEdit;
pEdit=(CEdit*)GetDlgItem(IDC_EDIT_RECV);
int i=pEdit->GetLineCount();
pEdit->LineScroll(i,0);
}
return(OL);
}
手工加入CTalkDialog::OnSend(),将指定缓冲区中的数据发送出去。
void CTalkDialog::OnSend()
{
UpdateData();
m_csSend.TrimLeft();
m_csSend.TrimRight();
if(!m_csSend.IsEmpty())
{
m_csSend=m_csName+″:″+m_csSend+″\r\n″;
int nCharSend=send(m_hSocket,m_csSend,m_csSend.GetLength(),0);
if(nCharSend=SOCKET_ERROR)
MessageBox(″ 发送数据错误!″,NULL,MB_OK);
}
m_csSend=″″;
UpdateData(FALSE);
CWnd *pEdit=GetDlgItem(IDC_EDIT_SEND);
pEdit->SetFocus();
return;
}
通过ClassWizard 增加 virtual function PreTranslateMessage(), 控制当按回车键时调用OnSend(),而不是执行缺省按钮的动作。
BOOL CTalkDialog::PreTranslateMessage(MSG*pMsg)
{
if(pMsg-> message==WM_KEYDOWN && pMsg-> wParam=VK_RETURN){
OnSend();
}
return CDialog::PreTranslateMessage(pMsg);
}
为了简化设计,用户名在客户端控制,服务端只进行简单的接收信息和“广播”此信息,不进行名字校验,也就是说,可以有同名客户登录到服务端。这个程序设计虽然简单,但是已经具备了聊天室的最基本的功能。服务端程序在SCO OpenServer 5.0.5 下编译通过,客户端程序在VC++6.0 下编译通过,在使用TCP/IP 协议的局域网上运行良好。
上述实例仅用于说明通过 Socket 编程接口能方便地实现跨系统通讯,而这种跨系统通讯在金融行业中有着越来越广泛的应用,利用它可以实现各种多媒体查询,数据传输,网络通讯等功能。因此对跨系统通讯的探讨是非常必要和有意义的,希望上述探讨对大家能有所启发。
如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。
现场直击|2021世界人工智能大会
直击5G创新地带,就在2021MWC上海
5G已至 转型当时——服务提供商如何把握转型的绝佳时机
寻找自己的Flag
华为开发者大会2020(Cloud)- 科技行者