概述
第一节理解套接字已经完成了tcp server的实现,但是处理完一个client请求之后就退出了。本节进行一下扩展。
理解tcp和udp
下图为tcp/IP协议栈的四层协议:
当然OSI规定了7层协议,但对于我们编写网络通讯程序来说理解这四层协议足够了
- 链路层是物理链接的标准化,定义LAN,WAN等标准
- IP层解决路径选择问题
- TCP/UDP层建立在IP层基础之上,决定传输方式
应用层是我们实现网络通讯时真正接触到的一层,前边的路径选择,数据确认等过程都被隐藏在套接字内部,我们只需要设计应用层协议即可。
基于tcp的服务器端/客户端
下图是服务器端和客户端的socket函数调用流程,阴影部分前边的章节介绍过,这里只介绍其他三个函数listen accept connect:
只有服务端已经调用了listen,客户端才能发出连接请求。
listen(sock,n) 第一参数就像门卫,也像分号台的一个护士,来安排请求连接的客户端。而第二个参数就是等候厅的大小。
accept自动产生与客户端实际连接的套接字,用于实际的数据通讯,不同于listen分号太的护士。
对于connect有一点说明,可以看到在server端我们通过bind函数指定了ip和端口号,而客户端确没有这么做。为什么呢?网络数据交换是必须要分配ip和端口的。其实对于客户端,在调用connect函数时由操作系统把本机ip和一个随机端口自动分配了。无需程序指定。
迭代服务端/客户端的实现
server代码如下:
// tcpserverwin.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>
#pragma comment (lib,"ws2_32.lib")
#define BUF_SZ 1024
int main(int argc, char* argv[])
{
WSADATA wsaData;
SOCKET hSocket,hClientSock;
SOCKADDR_IN servAddr,clientAddr;
int strlen,i;
//char message[] = "hello client, I am server\n";
char message[BUF_SZ];
int szClientAddr;
if(argc!=2){
printf("usage : %s port\n",argv[0]);
return 0;
}
if(WSAStartup(MAKEWORD(2,2),&wsaData)!=0){
printf("WSAStartup failed\n");
return 0;
}
hSocket = socket(PF_INET,SOCK_STREAM,0);
if(hSocket == INVALID_SOCKET){
printf("socket error\n");
return 0;
}
memset(&servAddr,0,sizeof(servAddr));
servAddr.sin_family = AF_INET;
servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
servAddr.sin_port = htons(atoi(argv[1]));
//int bind(hSocket,(SOCKADDR*)&servAddr,sizeof(servAddr));
if(bind(hSocket,(SOCKADDR*)&servAddr,sizeof(servAddr)) == SOCKET_ERROR){
printf("bind port [%d] error\n",atoi(argv[1]));
return 0;
}
if(listen(hSocket,5) == SOCKET_ERROR){
printf("listen error\n");
return 0;
}
szClientAddr = sizeof(clientAddr);
for (i=0;i<5;i++)
{
hClientSock = accept(hSocket,(SOCKADDR*)&clientAddr,&szClientAddr);
if(hClientSock == INVALID_SOCKET){
printf("accept error\n");
continue;
}
else{
printf("accept client:%d\n",i);
}
while((strlen=recv(hClientSock,message,BUF_SZ,0))!=0){
send(hClientSock,message,strlen,0);
}
closesocket(hClientSock);
}
closesocket(hSocket);
WSACleanup();
return 0;
}
client代码如下:
// tcpclientwin.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>
#pragma comment (lib,"ws2_32.lib")
#define BUF_SZ 1024
int main(int argc, char* argv[])
{
WSADATA wsaData;
SOCKET hSocket;
SOCKADDR_IN servAddr;
char message[BUF_SZ];
memset(message,0,sizeof(message));
int slen;
if(argc!=3){
printf("usage : %s ip port\n",argv[0]);
return 0;
}
if(WSAStartup(MAKEWORD(2,2),&wsaData)!=0){
printf("WSAStartup failed\n");
return 0;
}
hSocket = socket(PF_INET,SOCK_STREAM,0);
if(hSocket == INVALID_SOCKET){
printf("socket error\n");
return 0;
}
memset(&servAddr,0,sizeof(servAddr));
servAddr.sin_family = AF_INET;
servAddr.sin_addr.s_addr = inet_addr(argv[1]);
servAddr.sin_port = htons(atoi(argv[2]));
if(connect(hSocket,(SOCKADDR*)&servAddr,sizeof(servAddr)) == SOCKET_ERROR){
printf("connect error\n");
return 0;
}
for (;;)
{
fgets(message,BUF_SZ,stdin);
if(!strcmp(message,"q\n")){
break;
}
char msg[] = "hello server";
send(hSocket,msg,strlen(msg),0);
slen = recv(hSocket,message,sizeof(message)-1,0);
if (slen == -1)
{
printf("recv error\n");
}
printf("message from server:%s\n",message);
}
closesocket(hSocket);
WSACleanup();
return 0;
}