刚接触Socket网络编程,找了一个资料先学习一下。所以也贴出来了。
以下转载自:http://blog.csdn.net/Melody_1208/archive/2007/11/12/1880939.aspx
不知道本程序有没有价值,先将Winsock编程学会了再说。
在正式开始前先介绍一下客户端/服务器程序。很多网络程序都是采用的客户端/服务器模型,简称C/S模型。事实上,这种架构我们非常熟悉,浏览器和Web服务器之间就是C/S的关系。知名服务器和客户端之间的数据交互是按照一定的标准进行的,从TCP/IP体系结构来说属于应用层协议。与知名C/S应用相比,我们自己开发的C/S应用采用的是私有的应用协议,而不是公开标准(当然,你也可以使用知名的协议)。有了这点认识,我们再来看看网络C/S程序的要点。
1面向连接与无连接
服务器与客户端之间的数据通信是由底层的计算机网络通信协议TCP/IP来提供的,TCP/IP协议族为应用层服务提供了两种传输层服务:面向连接的TCP协议和无连接的UDP协议。因此,在开发自己的C/S系统时,首先要决定的是选择面向连接的还是无连接的传输协议。
TCP与UDP之间的差别是很明显的:UDP是一种非常简单的传输层协议,它并不能保证数据最终到达目的地;与此相比,TCP提供可靠的传输服务,确认,超时和重传机制确保了应用层数据会被可靠地传送到目的地,同时TCP还提供流量控制。
采用UDP还是采用TCP并没有一定的标准,通常只有在以下几种情况下采用UDP协议:
(1)C/S之间的数据交互非常简单,如标准服务daytime和echo。
(2)与数据丢失相比,系统更不能容忍超时重传所带来的时间耗费,典型的应用是音视频系统。
(3)系统架构要求采用组播或者广播报文的方式。
除此之外,如果要在系统中采用UDP协议,那么就必须设计良好的能保证数据可靠传输的应用层协议:要达到这个目标,确认,超时重传和流量控制或许都是必需的,而这正是TCP协议所提供的。
2并发与迭代
通常情况下,服务器需要同时向多个客户提供服务,而客户端通常只与一个服务器联系(一个常见的例外是WEB浏览器)。我们把能同时接受多个客户连接的服务器称为并发服务器(Concurrent Server),而把一次只能服务一个客户的称为迭代服务器(Iterative Server)。
迭代服务器通常用于提供一些简单的服务,如daytime,echo等。对于复杂服务的提供来说,长时间地停顿在一个客户的服务上而拒绝其它客户是不可取的。
有了以上的知识,我们开发一个客户端程序来做分析:
#pragma comment(lib,"ws2_32.lib")
#include <STDIO.H>
#include <WINSOCK2.H>
SOCKET g_sockClient=INVALID_SOCKET;
void usage();
BOOL WINAPI CtrlHandler(DWORD dwEvent);
int main(int argc,char *argv[])
{
unsigned long destAddr;
int nPort;
if(argc==2)
{
destAddr=inet_addr(argv[1]); // parse the IPV4 address into 32bit integer
// which suitable for the struct in_addr
if(destAddr==INADDR_NONE)
{
usage();
return -1;
}
nPort=23;
}
else
if(argc==3)
{
destAddr=inet_addr(argv[1]);
if(destAddr==INADDR_NONE)
{
usage();
return -1;
}
nPort=atoi(argv[2]);
if(nPort<=0||nPort>65535)
{
usage();
return -1;
}
}
else
{
usage();
return -1;
}
if(!SetConsoleCtrlHandler(CtrlHandler,TRUE))
{
printf("SetConsoleCtrlHandler: %d/n",GetLastError());
return -1;
}
WSADATA wsaData;
WSAStartup(WINSOCK_VERSION,&wsaData);
g_sockClient=socket(AF_INET,SOCK_STREAM,0);
if(g_sockClient==INVALID_SOCKET)
{
WSACleanup();
return -1;
}
struct sockaddr_in to;
memset(&to,0,sizeof(to));
to.sin_addr.S_un.S_addr=destAddr;
to.sin_family=AF_INET; // set the address family
to.sin_port=htons(nPort); // change the unsigned short into "Big-Endian"
printf("connecting %s:%d......",inet_ntoa(to.sin_addr),nPort);
// connect the destinate ip address
if(connect(g_sockClient,(struct sockaddr *)&to,sizeof(to))==SOCKET_ERROR)
{
if(g_sockClient!=INVALID_SOCKET)
closesocket(g_sockClient);
printf("Failed.(connect %d)/n",WSAGetLastError());
WSACleanup();
return -1;
}
else
printf("Successfully./nINPUT:/n");
char bufa[83];
char bufb[1000];
fd_set readSet;
struct timeval tv;
int ret,len;
while(1) // loop and handle the network event
{
memset(bufa,0,83);
gets(bufa);
len=strlen(bufa);
if(len>80)
len=80;
bufa[len]='/r';
bufa[len+1]='/n';
bufa[len+2]=0;
ret=send(g_sockClient,bufa,strlen(bufa),0); // send the data of bufa to the destinate address
if(ret==SOCKET_ERROR)
{
printf("send: %d/n",WSAGetLastError());
break;
}
FD_ZERO(&readSet);
FD_SET(g_sockClient,&readSet);
tv.tv_sec=3;
tv.tv_usec=0;
ret=select(0,&readSet,NULL,NULL,&tv);
if(ret==SOCKET_ERROR)
{
printf("select: %d/n",WSAGetLastError());
break;
}
if(ret==0)
{
printf("Timeout,No Response From Server./n");
break;
}
if(FD_ISSET(g_sockClient,&readSet))
{
memset(bufb,0,1000);
ret=recv(g_sockClient,bufb,1000,0);
if(ret==SOCKET_ERROR)
{
printf("recv: %d/n",WSAGetLastError());
break;
}
else
printf("%s/n",bufb);
}
}
if(g_sockClient!=INVALID_SOCKET)
closesocket(g_sockClient);
WSACleanup();
printf("Stopped./n");
return 0;
}
/* print the usage of mytelnet*/
void usage()
{
printf("usage:/tmytelnet x.x.x.x port/t(0<port<65535)/n");
}
BOOL WINAPI CtrlHandler(DWORD dwEvent)
{
switch(dwEvent)
{
case CTRL_C_EVENT:
case CTRL_LOGOFF_EVENT:
case CTRL_SHUTDOWN_EVENT:
case CTRL_CLOSE_EVENT:
printf("Stopping....../n");
closesocket(g_sockClient);
g_sockClient=INVALID_SOCKET;
break;
default:
return FALSE;
}
return TRUE;
}
接下来我们连编该程序,并尝试登陆清华的bbs,输入telnet 161.111.8.238,然后回车。我得到的画面是这样的:
这个好像有点不对劲,呵呵,具体细节以后再讨论。接下来我们看看这个客户端的结构。
1.我们使用#pargma comment(lib,"ws2_32.lib")指示连接器查找并使用ws2_32.lib静态库。如果不使用编码方式,也可以选择在项目设置的Link选项中加入ws2_32.lib。
2.SOCKET g_sockClient=INVALID_SOCKET;声明套接口类型的全局变量g_sockClient。
3.读取输入参数目标IP和端口,分别保存在destAddr和nPort中。
4.调用SetConsoleCtrlHandler函数为MyTelnet设置某些特殊事件的响应函数,这些特殊事件有CTRL_C_EVENT,CTRL_CLOSE_EVENT等。
5.调用WSAStartup函数,初始化Winsock。
6.创建客户端的TCP套接口g_sockClient。
7.填充INET地址结构to,即设定所需连接的TCP服务器的IP地址和端口。
8.根据地址to,连接TCP服务器。
9.在循环体内,我们从控制台读取用户输入数据,发送给服务器,然后接收服务器的反馈并打印。
10.ret=select(0,&readSet,NULL,NULL,&tv);使用了select函数进行超时控制。
11.调用closesocket函数关闭客户端套接口g_sockClient。
12.调用WSACleanup函数,结束Winsock的使用。
BOOL WINAPI CtrlHandler(DWORD dwEvent)为控制台事件的响应函数。如果MyTelnet接受到CTRL_C_EVENT,CTRL_LOGOFF_EVENT,CTRL_SHUTDOWN_EVENT或者CTRL_CLOSE_EVENT事件,那么客户端关闭套接口g_sockClient(将导致主程序中关于g_sockClient的Winsock函数调用出错,从而退出循环),并将其赋值为INVALID_SOCKET。