本篇,纪录一个在 windows上使用 socket进行通信的例子,代码源自于网上。由于时间过去挺久了,当时我也没有加书签,现在暂时还不好找出处。 文中给出一些关键代码片段,一方面用于巩固我所学的知识,另一方面,用于纵向的技术对比,加深理解。完整的地址在这里:地址。
首先看看项目结构:
服务端的关键代码:
int main(){
//加载Winsock库,初始化socket资源
initialization();
//创建套接字
SOCKET s_server;
s_server = socket(AF_INET,SOCK_STREAM,0);
//绑定信息
bindInfo(&s_server);
//将socket设置为lisen状态
changtoListen(&s_server);
SOCKET commandSock;
responseAccept(commandSock,&s_server);
//通信
onInteract(&commandSock);
//结束
onClose(&commandSock,&s_server);
return 0;
}
总体而言,就是经过标准的socket步骤: 生成本地socket->bind() -> listen() -> accept() -> 通信传输 -> 关闭;
这个过程中,关键点在于accept(),在本次代码中,它是同步阻塞的,代码如下:
//响应连接状态
int responseAccept(SOCKET& _sock,SOCKET* _s_server){
int len = sizeof(SOCKADDR);
SOCKADDR_IN accept_addr;
SOCKET s_accept= accept(*_s_server, (SOCKADDR *)&accept_addr, &len);
if (s_accept==SOCKET_ERROR){
std::cout << "连接失败!错误码:"<<WSAGetLastError() <<std::endl;
WSACleanup();
return 0;
}
_sock = s_accept;
std::cout << "连接建立,准备接受数据!" <<std::endl;
}
接收的结果是 套接字对象;同时,这个接收的过程是 阻塞的。 与客户端的所有通信将基于当前接收的套接字对象进行。
我将 服务器与客户端的通信 抽象为一个方法,称之为 互动 onInteract(),代码如下:
//利用返回的套接字进行通信
void onInteract(SOCKET* s_accept){
int recv_len =-1;
int send_len = -1;
char recv_buf[100];
char send_buf[100];
while (1){
recv_len = recv(*s_accept,recv_buf,100,0);
if (recv_len<0){
std::cout <<"接受失败!错误码:"<<WSAGetLastError()<<std::endl;
break;
}else if(recv_len==0){
std::cout <<"会话结束!"<<std::endl;
} else{
std::cout <<"客户端信息:"<<recv_buf<<std::endl;
}
std::cout <<"请输入回复信息:";
std::cin>> send_buf;
send_len = send(*s_accept,send_buf,100,0);
if(send_len<0){
std::cout <<"发送失败!错误码:"<<WSAGetLastError() <<std::endl;
break;
}
}
}
本地代码的关键点在于: recv(),与 send() 都是阻塞的方式。 它的表现是: 一次发送,一次接收 如此循环往复。 此外,还应对接收到的信息进行判别,用于控制通信的结束,也就形成了实际上的 应用层协议(但是在本例子中还未加,由于此时不想修改代码,确实没有精力)。
接着,再看一看客户端的代码:
int main(){
initialization();
SOCKET s_server;
s_server = socket(AF_INET,SOCK_STREAM,0);
onConnect(s_server);
onActive(s_server);
onClose(s_server);
return 0;
}
它的标准流程则是: 生成本地socket -> connect() -> 消息通信的过程 -> 关闭。
这其中的注意的是: connect()该方法是立即返回的,连接由tcp/udp进行控制。 之后通信的过程,我依然是抽象成的一个方法叫做: onActive(),代码如下:
//与服务器进行通信
void onActive(SOCKET& s_server){
char send_buf[100];
char recv_buf[100];
int recv_len;
int send_len;
while (1){
std::cout<<"请输入发送信息:";
std::cin>>send_buf;
send_len = send(s_server,send_buf,100,0);
if (send_len<0){
std::cout <<"发送失败!"<<std::endl;
break;
}
recv_len = recv(s_server,recv_buf,100,0);
if (recv_len<0){
std::cout <<"接收失败!"<<std::endl;
break;
} else{
std::cout <<"服务端信息:"<<recv_buf<<std::endl;
}
}
}
其中的关键仍然是send()方法,recv()方法,它们当然也是阻塞的。
最终的效果如下:
总结:
1.在实践过程中,空格字符会被 处理成 分段操作,譬如 "ping hello", 会理解为两次 send。这说明了在socket的读写字节流中,控制字符有可能大部分要经过转义。
2.当前方式实现中,读写操作是交替运行的。并且,读写操作是阻塞的(发送和接收是一种另类的读写操作)。
3.当前实现中,服务器是单线程的,不要搞混淆了,接收到的是 socket,而不是thread。 这也意味着,一次只能接入一个会话(或者接收到的多个会话进行线性处理。)。(因为通话的过程是基于接收到的socket句柄而进行的.) 。 此外,socket与线程并没有必然联系,它们实际上代表了两种资源。