UID 1
精华
积分 76365
威望 点
宅币 个
贡献 次
宅之契约 份
最后登录 1970-1-1
在线时间 小时
Winsock是什么?
Winsock是用来借助网络,让多台设备互相发送数据的一个库。我们用浏览器上网的原理,就是浏览器通过使用Winsock来发送请求给服务器,然后服务器通过Winsock把网页的内容发给浏览器。
或者举个例子,我们使用QQ聊天的时候,是什么东西把我们要传达给对方的话发送到对方电脑上的呢?是Winsock。
这么说大家应该懂什么是Winsock了吧?就是不同的电脑之间互相发送信息的一个工具。Winsock具有可移植性,能在不同的环境下使用(除了Windows系统以外,安卓手机、PSP游戏机、苹果电脑、各种PAD等都支持Winsock)
Winsock是C语言的库,C艹也能用。当然别的语言也有使用Winsock的方法,但是我这里就不赘述了。我主要介绍C语言如何使用Winsock进行数据传输。
对于我的教程有什么不懂的请查询MSDN。
1、先了解一下网络是什么东西。
图中那个大圈上的用户都是用猫连的网,因此他们的IP地址就是这个大圈内的公网IP。
路由器的作用是让多个用户只通过一个上网账号就能上网。每个路由器都连着一个圈,这个圈就是这些共用一个上网账号的复数个用户。
通过设置路由器的转发机制,可以把所有发往路由器的数据包全部发到局域网的其中一台电脑。这样可以直接实现直连。
许多局域网游戏,都可以通过这个方法来联机。比如KKND2。
所谓桥接其实和路由器的性质差不多。
2、Winsock的大致使用流程:
①初始化Winsock(Windows下调用WSAStartup进行初始化,Linux下貌似不需要)
②创建一个“套接字”。你可以把“套接字”理解为“用来收发数据包的那么一个东西”、“‘套接字’是用来发东西的”
③用完了的“套接字”记得关闭就行。不关闭会妨碍别的程序的通信。使用closesocket把不用的套接字关闭掉吧。
④用完Winsock之后,Windows下记得调用WSACleanup结束你对Winsock的使用。
3、一些概念
①IP地址:用来定位一个电脑的位置。
②域名:所谓域名就是……举个例子,“www.baidu.com ”、“www.0xaa55.com ”这样的东西就是域名。所谓“域名解析”就是把域名变成IP地址。
③端口:一个电脑(或别的设备)进行网络数据传输的通道。不同通道之间互相不影响。
本教程主要是以VC6编程为标准的,包括VC6建立工程的细节在内。
第一步:建立工程
1、建立一个Windows Console Application(这里不需要用到GUI,因此用CUI来演示详细的效果。)
我这里建立了一个名为Chat的工程,是空工程,这样便于设置。
2、创建一个新的C源码文件。点开文件视图然后点“文件->新建->C++ Source File”文件名写Entry.c 。注意必须是Entry.c 而不是Entry.cpp
3、快速敲入以下代码为后面的教程做好准备:#include<stdio.h>
#include<winsock2.h>
int main(int argc,char**argv)
{
return 0;
} 复制代码 4、设置工程链接的库:ws2_32.lib(注意别忘了同时设置Debug和Release生成的时候链接这个库)
注:这一步可以用一行代码【#pragma comment ( lib ,"ws2_32.lib") //不需要加分号 】代替。
5、因为我认为使用Winsock很自然需要用到多线程,因此我设置使用多线程的VC运行库(注意别忘了同时设置Debug和Release使用多线程库,注意Debug要选择Debug版,Release不要选择Debug版)
6、因为是个很小的工程,所以我们不需要预编译头。
现在开始讲Winsock的详细教程。
Windows下在使用Winsock前你必须初始化Winsock,通过调用函数WSAStartup()来完成初始化。Windows下Winsock出现任何错误都可以用WSAGetLastError取得错误的错误代码。
WSAStartup这个函数的特点:
1、不调用它,你就用不了其它所有的Winsock函数。
2、调用它需要提供一个WSADATA结构体来接收Winsock的一些信息比如版本号什么的。
3、调用成功返回1,否则返回0.
原型:int WSAStartup(
WORD wVersionRequested,
LPWSADATA lpWSAData
); 复制代码 调用范例:WSADATA wsaData;
if(WSAStartup(WINSOCK_VERSION,wsaData))//出错返回非零
{
fprintf(stderr,"Initialize Winsock failed.\nWSAGetLastError=%u\n",WSAGetLastError());//输出错误信息到stderr
return 1;//返回1
} 复制代码 当然,和它对应的收尾函数是WSACleanup()。用完Winsock之后记得调用它来结束你对Winsock的使用。不调用它的话,你的程序退出后可能会暂时引起整个系统的Winsock的紊乱(比如玩游戏无法联机等问题)。比较蛋疼。
大家可能会问我为什么要返回1?嘛,那是因为我假定这个函数是写在int main里面的,通常情况下main函数返回0表示整个程序都成功运行了。那么这里因为出错了所以自然返回一个非零值啦。
WSACleanup()的原型:直接调用WSACleanup();就能完成对它的调用。
刚才只是讲了Winsock的初始化与释放。现在来介绍Winsock联网常用的两个协议:TCP/IP协议与UDP协议。
这两个协议采用不同的数据传输方式,一种是“有保障的”,一种是“没有保障的”。其中TCP/IP是“有保障的”,而UDP是“没有保障的”。
UDP教程:
UDP的概述:
UDP协议被称作“数据报”,因为你用它发送数据的时候不需要连接。假设111.222.33.44这个IP地址有台电脑,我要发送一个数据包到这个电脑上,我不需要连接它,我直接发送就行。而那个电脑如果不是接受数据的状态的话,它就会错过这个数据包,从而导致数据包丢失。可以看出UDP是很直接的传输方式。Winsock设置的默认的缓冲区大小是8 KB,如果我发送16 KB的数据包出去,那么事实上只有前面8 KB的数据被发送了出去,后面的都没有发送出去。UDP一次只能传输8 KB的数据包,而且不能保证对方一定能接收到数据包。这就是所谓的“没有保障的”。
TCP/IP就和UDP不一样,TCP/IP在发送数据包以前必须先建立连接,只有成功建立连接到对方电脑后,才能互相收发数据。但是就算你在发送数据的时候,对方电脑并没有处于接受数据的状态,数据也不会丢失,只要对方电脑开始接受数据,我们的数据包就能传达到对方电脑里,可以保障数据包不容易丢失。这就是所谓的“有保障的”。但是TCP/IP比UDP略慢。我觉得“有保障的”比较适合新手入门。
大致说一下UDP方式怎么进行数据传输:
1、电脑A创建套接字,电脑B创建套接字
2、电脑A设置好自己要使用的端口号,电脑B设置成一样的端口号。
3、电脑A准备接收数据,然后电脑B往电脑A发送数据,电脑A收到,或者电脑B准备接收数据,然后电脑A往电脑B发送数据,电脑B收到。
4、传输完了以后,关闭套接字。
因为UDP不需要事先建立连接,因此UDP的发送方式是很自由的。
首先我们需要建立一个叫“套接字”的东西。我们要借助它进行数据的发送与接收。
套接字是SOCKET类型(其实Windows下是int类型,但是请大家声明为SOCKET类型以保证可移植性)。
产生新的套接字是通过调用socket函数或accept函数。这里是UDP教程,accept是TCP/IP的函数,因此请先无视它。
socket的原型:SOCKET socket(
int af,
int type,
int protocol
); 复制代码 参数说明:
1、int af这里请传入AF_INET这个值。你不需要传入别的值。
2、int type这里有个选项:SOCK_STREAM或SOCK_DGRAM。其中SOCK_STREAM指的是具有连接性质的协议比如TCP/IP协议,而SOCK_DGRAM指的是数据报性质的协议比如UDP协议。请传入SOCK_DGRAM这个值,因为这里我是在介绍UDP的玩法。
3、int protocol这里是选择特定协议的。如果你不设置协议类型的话(也就是传入0)也可以。其实我觉得没必要非得指定我们用什么协议,反正type参数已经指明了我们想要的连接方式。
范例代码:SOCKET sockMain=socket(AF_INET,SOCK_DGRAM,0);//UDP
if(INVALID_SOCKET==sockMain)
{
fprintf(stderr,"Create SOCKET failed.\nWSAGetLastError=%u\n",WSAGetLastError());//输出错误信息到stderr
return 1;//返回1
} 复制代码 当然SOCKET这种东西创建出来也是要释放掉的,所以请大家在不需要使用SOCKET的时候释放它。通过调用closesocket()来释放它。
closesocket的原型:int closesocket(
SOCKET s
); 复制代码 刚才也只是讲了如何建立一个UDP的SOCKET,现在开始讲如何收发数据包。
接收数据包使用recvfrom,发送数据包使用sendto。先来看看函数原型:
sendto函数:int sendto(
SOCKET s,
const char FAR *buf,
int len,
int flags,
const struct sockaddr FAR *to,
int tolen
); 复制代码 recvfrom函数:int recvfrom(
SOCKET s,
char FAR* buf,
int len,
int flags,
struct sockaddr FAR *from,
int FAR *fromlen
); 复制代码 现在来解释一下sendto函数的各个参数的使用:
1、SOCKET s指定你要发送数据所使用的套接字。
2、const char FAR *buf这个是要发送的缓冲区的指针。
3、int len要发送的字节数。
4、int flags设置发送的时候的一些额外的要求,这里只能有两个选项:0(没有别的要求),MSG_DONTROUTE(不要让数据包经过路由。这个要求可能会被网卡驱动无视掉。)
5、const struct sockaddr FAR *to这个参数是一个SOCKADDR结构体的指针,作用是告诉系统你要把数据包发往哪里。SOCKADDR结构体的作用就是指定一个端口和IP地址。
6、int tolen这个参数指定了SOCKADDR结构体的大小。请传入sizeof(SOCKADDR_IN)
recvfrom函数的各个参数的使用:
1、SOCKET s指定你要接收数据所使用的套接字。
2、const char FAR *buf这个是用来接收数据的缓冲区的指针。
3、int len指定缓冲区的大小,换句话说就是你这次最大能接收多少个字节。这个值最好不要大于8192因为Winsock默认的缓冲区大小就是8192字节,你这个值再大你也接收不了那么多信息。
4、int flags设置你接收数据时的一些额外的要求,这里只能有两个选项:0(没有别的要求),MSG_PEEK(不要把数据从缓冲区中删除,函数返回你应该接收的数据包的字节数)
5、struct sockaddr FAR *from这个参数指向一个空的SOCKADDR结构体,然后你就可以通过这个结构体来判断数据是从哪里发来的。可以为NULL
6、int FAR *fromlen这个参数指向一个int指针,获取你提供的SOCKADDR结构体的大小同时告诉你返回的SOCKADDR结构体的大小。如果上一个参数是NULL,这个参数也必须是NULL
大家可以注意到如果你直接调用recvfrom函数,你并没有指定你这个套接字的端口号,因此你无法接收到数据。因此你需要绑定端口号。请使用bind函数来绑定端口号。
bind函数原型:int bind(
SOCKET s,
const struct sockaddr FAR *name,
int namelen
); 复制代码 bind函数的各个参数的作用:
1、SOCKET s指定你要绑定IP地址和端口号的套接字。
2、const struct sockaddr FAR *name指定你要绑定的端口号和IP地址。
3、int namelen指定你给出的SOCKADDR结构体的大小。
sendto函数会在发送数据包的时候自动给你的套接字绑定IP地址和端口号,所以调用过sendto函数的SOCKET就不需要bind了。
当你调用recvfrom函数的时候,这个函数会一直卡住不动,直到有人用sendto发送数据包到你这里,你的recvfrom才会返回。因此Winsock适合多线程编程。
我来画个图告诉大家UDP协议的使用流程。
可以看出UDP不需要连接。
细节:bind和sendto都能给SOCKET绑定IP地址和端口。
recvfrom函数在运行的时候会一直等数据包的到来,直到数据包到来了,它才返回。而sendto则会立即返回。
当你在调用recvfrom的时候,无论你准备了多大的缓冲区,recvfrom只要收到了数据包就一定会立即返回。
当你调用sendto发送超过8 KB的数据包的时候,只有前8 KB的数据被成功发送。后面的数据都丢失了。
当一方发送数据的时候如果另一方并没有调用recvfrom,那么另一方将无法接收到数据包。
就像下图这样:
调用sendto、recvfrom和bind这些函数的时候你需要传入一个结构体指针叫“SOCKADDR*”,这东西怎么弄呢?请看下面的代码:SOCKADDR_IN sAddr;//我们使用SOCKADDR_IN而不是SOCKADDR
memset(&sAddr,0,sizeof(sAddr));//先把它清零
sAddr.sin_family=AF_INET;//必须设置这个成员而且值必须是AF_INET。
sAddr.sin_port=htons(端口号);//htons负责把一个short转换成Big-Endian,类似的函数还有htonl(把long转换成Big-Endian)
sAddr.sin_addr.s_addr=htonl(IP地址);//IP地址是一个DWORD类型值,举个例,127.0.0.1这个IP地址用DWORD表示就是0x7F000001。
//搞定 复制代码 IP地址和端口号都是按照Big-Endian存储的。
得到IP地址的方法有很多,一个比较简单的方法是调用inet_addr函数,举例:
sAddr.sin_addr.s_addr=inet_addr("127.0.0.1");其中inet_addr函数返回0x0100007F(inet_addr会自动帮你弄成Big-Endian)
这里给出代码来实际演示一下一个简单的应答程序。
玩法:
按下Ctrl+T,输入IP地址然后按下回车设置自己的发送目标,然后输入字符串按下Ctrl+Z发送。
这个程序会自动接收信息并显示。
按下Ctrl+C结束程序。#include<stdio.h>
#include<errno.h>
#include<signal.h>
#include<process.h>
#include<winsock2.h>
//定义自己的宏来保证可移植性
//在Windows平台使用Winsock
#define USE_WSAAPI
//如果出错,输出errno信息
#define USE_ERRNO
const u_short g_sPort=11037;//用这个数字做端口号
char g_Exit=0;//全程退出时将其置1
SOCKET g_sockMain=INVALID_SOCKET;//全局只使用一个套接字
SOCKADDR_IN g_saCurTarget={0};//当前的发送目标
//============================================================================
//出错后调用这个函数输出错误原因到stderr
//参数:出错的附加信息
//如果定义了USE_ERRNO,这个函数还会输出stderr的错误信息
//如果定义了USE_WSAAPI,这个函数还会输出WSAGetLastError的错误代号
//============================================================================
void ErrOut(const char*szErr,int iRet)
{
fputs(szErr,stderr);
fprintf(stderr,"(Function returned %d)\n",iRet);
# ifdef USE_ERRNO
fprintf(stderr,"errno=%d(%s)\n",errno,strerror(errno));//先输出errno,因为我可以确定errno不会影响到WSAGetLastError
# endif
# ifdef USE_WSAAPI
fprintf(stderr,"WSAGetLastError=%u\n",WSAGetLastError());
# endif
}
//============================================================================
//初始化Winsock的函数
//不需要参数
//返回0表示初始化失败,返回1表示初始化成功
//============================================================================
int InitWinsock(void)
{
# ifdef USE_WSAAPI//Windows下需要调用WSAStartup进行初始化
int iRet;
WSADATA wsaData;
iRet=WSAStartup(WINSOCK_VERSION,&wsaData);//WSAStartup失败返回非零
if(iRet)
{
ErrOut(""WSAStartup" failed.",iRet);
return 0;//出错返回0
}
# endif
return 1;//成功返回1
}
//============================================================================
//关闭Winsock的函数
//返回0表示关闭失败,返回1表示关闭成功
//============================================================================
int ShutdownWinsock(void)
{
# ifdef USE_WSAAPI//Windows下需要调用WSAStartup进行关闭操作
int iRet;
iRet=WSACleanup();//WSACleanup失败返回非零
if(iRet)
{
ErrOut(""WSACleanup" failed.",iRet);
return 0;//出错返回0
}
# endif
return 1;//成功返回1
}
//============================================================================
//线程处理程序:专门负责接收数据包并显示
//============================================================================
void ReceiverThread(void*p)
{
char szBuf[0x2000];//接收缓冲区
SOCKADDR_IN sAddr={0};
int iFromLen=sizeof(sAddr);
int iRet=0;
printf("Now Data Receiver is able to accept data from port %u.\n",g_sPort);
sAddr.sin_family=AF_INET;
sAddr.sin_port=htons(g_sPort);
sAddr.sin_addr.s_addr=htonl(INADDR_ANY);//INADDR的宏都是LE的,因此需要用htonl
iRet=bind(g_sockMain,(SOCKADDR*)&sAddr,sizeof(sAddr));//先绑定端口号
if(iRet)
{
ErrOut(""ReceiverThread" failed to "bind".",iRet);
return;
}
memset(szBuf,0,sizeof(szBuf));
iRet=recvfrom(g_sockMain,szBuf,sizeof(szBuf),0,(SOCKADDR*)&sAddr,&iFromLen);
while(iRet>0)
{
printf("Received from:%d.%d.%d.%d\n%s\n",//先打印来源然后打印内容
sAddr.sin_addr.s_net,//IP地址
sAddr.sin_addr.s_host,
sAddr.sin_addr.s_lh,
sAddr.sin_addr.s_impno,
szBuf);
if(g_Exit)
{
printf("Data Receiver is stopped.\n");
return;
}
iFromLen=sizeof(sAddr);
memset(szBuf,0,sizeof(szBuf));
iRet=recvfrom(g_sockMain,szBuf,sizeof(szBuf),0,(SOCKADDR*)&sAddr,&iFromLen);
}
ErrOut("recvfrom failed.",iRet);
}
//============================================================================
//信号处理程序:用于处理Ctrl+C
//============================================================================
void SignalProc(int iSignal)
{
switch(iSignal)
{
case SIGINT:
{
SOCKADDR_IN sSelf={0};//自己给自己发送消息来使接收消息的线程继续运行。
char szQuit[]="Ctrl+C detected. Quitting.\n";
if(g_sockMain!=INVALID_SOCKET)
{
int iRet;
sSelf.sin_family=AF_INET;
sSelf.sin_port=htons(g_sPort);
sSelf.sin_addr.s_addr=htonl(INADDR_LOOPBACK);
iRet=sendto(g_sockMain,szQuit,sizeof(szQuit),0,(SOCKADDR*)&sSelf,sizeof(sSelf));
if(iRet<=0)
ErrOut(""sendto" failed.",iRet);
}
g_Exit=1;
fflush(stdin);
puts(szQuit);
}
break;
}
}
//============================================================================
//整个程序的入口点
//目前不打算把程序的运行结果输出到ERRORLEVEL
//也不打算使用参数
//============================================================================
void main(void)//这个程序不使用参数。
{
char szBuf[0x2000];
size_t nInputLen=0;
int iRet;
puts(
"First you need to press CTRL+T, input an IP address, press ENTER, and press CTRL+Z to specify your target IP address.\n"
"Then you can input any text data and press CTRL+Z to send this string to your target.\n"
"Press CTRL+C to exit this program.\n"
"You should to specify the address first.");//Ctrl+T会获得控制字符0x14
signal(SIGINT,SignalProc);//设置Ctrl+C的信号处理程序
if(InitWinsock())//初始化Socket
{
g_sockMain=socket(AF_INET,SOCK_DGRAM,0);//创建主要的套接字
if(g_sockMain==INVALID_SOCKET)
{
ErrOut(""socket" failed.",g_sockMain);//创建失败的处理
ShutdownWinsock();
return;
}
_beginthread(ReceiverThread,0,(void*)g_sockMain);//建立一个新线程来接收发来的数据包
do
{
nInputLen=fread(szBuf,1,sizeof(szBuf),stdin);//从stdin读取数据
if(szBuf[0]==0x14)//第一个字符如果是Ctrl+T
{
char *pEnd;
pEnd=strchr(szBuf+1,'\r');
if(pEnd)*pEnd=0;
pEnd=strchr(szBuf+1,'\n');
if(pEnd)*pEnd=0;
g_saCurTarget.sin_family=AF_INET;
g_saCurTarget.sin_port=htons(g_sPort);
g_saCurTarget.sin_addr.s_addr=inet_addr(szBuf+1);//设置IP地址
printf("Target IP=%d.%d.%d.%d\n",
g_saCurTarget.sin_addr.s_net,
g_saCurTarget.sin_addr.s_host,
g_saCurTarget.sin_addr.s_lh,
g_saCurTarget.sin_addr.s_impno);
}
else//否则发送数据
{
if(g_saCurTarget.sin_family!=AF_INET)//如果没设置IP地址
{
fputs("You need to specify a target first.\n"//提示要设置IP地址
"Please press CTRL+T, input an IP address, press ENTER, and press CTRL+Z to specify your target IP address.",stderr);
}
else
{
szBuf[nInputLen]=0;//设置文本结尾的\0
iRet=sendto(g_sockMain,szBuf,nInputLen,0,(SOCKADDR*)&g_saCurTarget,sizeof(g_saCurTarget));//发送
if(iRet<=0)
ErrOut(""sendto" failed.",iRet);
}
}
}while(!g_Exit);
ShutdownWinsock();
}
} 复制代码
实例下载地址:TCP/IP教程:
TCP/IP和UDP的区别在于TCP/IP多了一个连接的过程。
TCP/IP进行数据传输的方式:
1、电脑A建立套接字,电脑B也建立套接字
2、电脑A设置好端口,等待别的电脑去连接电脑A。
3、电脑B连接电脑A
4、电脑B准备接受数据,电脑A发送数据,电脑B收到,或者电脑A准备接受数据,电脑B发送数据,电脑A收到
5、传输完了以后,关闭套接字(其中一方关闭套接字就会导致连接断开。)
其中,等待别的电脑连入的叫“主机端”,而去连接别的电脑的叫“客户端”。说白了,“客户端”是“攻”,“主机端”是“受”。
我来画个图,演示一下TCP/IP的使用过程。
可以看出,就算电脑2在发送的时候电脑1并没有准备好要接收数据,电脑1调用recv仍然能接收到数据包。
TCP/IP使用send和recv函数收发数据包。
你可以一次性send超过8 KB的数据包,但是你需要通过分批recv来接收完所有的数据包。
可以通过ioctlsocket函数或给recv函数指定MSG_PEEK来获取待接收数据的数量。
这里给出两个函数的原型:
send的原型:int send(
SOCKET s,
const char FAR *buf,
int len,
int flags
); 复制代码 recv的原型:int recv(
SOCKET s,
char FAR *buf,
int len,
int flags
); 复制代码 ioctlsocket的原型:int ioctlsocket(
SOCKET s,
long cmd,
u_long FAR *argp
); 复制代码 参数详细信息:
send的参数:
SOCKET s指定你用来发送的套接字。
const char FAR *buf指定你要发送的数据的指针。
int len你要发送的数据的长度。
int flags你的额外要求。可选:0(没有别的要求),MSG_OOB(不通过主线发送),MSG_DONTROUTE(数据包不经过路由,这个要求可能会被网卡驱动无视)
recv的参数:
SOCKET s指定你用来发送的套接字。
const char FAR *buf指定你要发送的数据的指针。
int len你要发送的数据的长度。
int flags你的额外要求。可选:0(没有别的要求),MSG_OOB(接收不通过主线发送的数据),MSG_PEEK(不从缓冲区中删除数据)
ioctlsocket的参数:
SOCKET s指定你用来发送的套接字。
long cmd你的命令,可选择的值:FIONBIO(使套接字的函数不会卡住),FIONREAD(待接收的数据包大小),SIOCATMARK(检查有没有不通过主线发送的数据)
这里介绍一个概念:OOB数据包
所谓OOB(Out-Of-Band)指的是,你通过OOB方式发送的数据包必须通过OOB方式接收。你不通过OOB发送的数据包则不必用OOB方式接收。
可以看出send和recv都不必指定目标。
光说了如何发送数据包。现在开始讲如何建立连接。
首先,TCP/IP要区分“客户机”和“主机”
主机先建立套接字(socket函数),然后用bind函数绑定端口号,接着调用listen函数使主机进入侦听模式,最后调用accept函数等待客户机连接到主机。
客户机建立套接字(socket函数),然后调用connect函数向主机发起连接。注意主机必须在客户机调用connect之前调用accept,否则主机无法接收到客户机的连接请求。
accept是一个会卡住的函数(Blocking call)。调用它后,它会一直卡住,直到有客户端连接。
客户端连接的时候,accept会返回一个SOCKET,同时返回客户端的IP地址。这个时候主机端就可以通过这个返回的SOCKET和客户机进行交互。
accept函数原型如下:SOCKET accept(
SOCKET s,
struct sockaddr FAR *addr,
int FAR *addrlen
); 复制代码 参数信息:
SOCKET s指定侦听中的套接字。
struct sockaddr FAR *addr返回客户机的IP地址和端口,可以为NULL
int FAR *addrlen返回客户机IP地址和端口那个结构体的大小……(很绕口,大致意思就是上面那个struct sockaddr FAR *addr的大小了)如果上一个参数是NULL,这个参数也必须是NULL
accept是受,那么connect是攻。
connect的原型:int connect(
SOCKET s,
const struct sockaddr FAR *name,
int namelen
); 复制代码 connect会立即返回,不会卡住。
参数信息:
SOCKET s指定你用来连接的套接字
const struct sockaddr FAR *name指定你的连接目标
int namelen指定上面那个结构体的大小。
因为accept函数返回的套接字是主机端和客户端进行通讯用的套接字,因此主机端可以同时和多个客户端进行通讯。
下面我画张图来演示这样的关系。
主机端建立的套接字只是用来让客户机来连接。
这样一说就明白了吧?
最后放上代码:C语言通过Winsock下载网页内容的代码。
#include<stdio.h>
#include<winsock2.h>
#define BUF_SIZE 0x2000
int main(int argc,char**argv)
{
WSADATA wsaData;
char szUrl[0x1000]={0};
char szBuf[BUF_SIZE+1]={0};
if(WSAStartup(WINSOCK_VERSION,&wsaData))
{
fputs("初始化Winsock失败。\n",stderr);
return 1;
}
for(;;)
{
char *pDir=NULL;
struct hostent* hAddr=NULL;
SOCKADDR_IN sAddr={0};
SOCKET sConnect=INVALID_SOCKET;
int nSendLen=0;
int nRecv=0;
printf("请输入网址。\n");
gets(szUrl);
if(!strlen(szUrl))
{
fputs("没有输入网址,程序退出。\n",stderr);
goto BadEnd;
}
if(strnicmp(szUrl,"http://",7))
{
fputs("请输入以http://开头的网址……\n",stderr);
continue;
}
if(pDir=strchr(szUrl+7,'/'))
{
*pDir++=0;
printf("网址:%s\n目录:%s\n",szUrl+7,pDir);
}
else
printf("网址:%s\n",szUrl+7);
hAddr=gethostbyname(szUrl+7);
if(!hAddr)
{
fprintf(stderr,"域名解析失败。\nWSAGetLastError=%u\n",WSAGetLastError());
continue;
}
printf("目标IP地址:%u.%u.%u.%u\n",
(unsigned char)(hAddr->h_addr_list[0][0]),
(unsigned char)(hAddr->h_addr_list[0][1]),
(unsigned char)(hAddr->h_addr_list[0][2]),
(unsigned char)(hAddr->h_addr_list[0][3]));
sAddr.sin_family=AF_INET;
sAddr.sin_port=htons(80);
sAddr.sin_addr.s_addr=*(u_long*)(hAddr->h_addr_list[0]);
sConnect=socket(AF_INET,SOCK_STREAM,0);
if(sConnect==INVALID_SOCKET)
{
fprintf(stderr,"无法创建SOCKET套接字。\nWSAGetLastError=%u\n",WSAGetLastError());
goto BadEnd;
}
if(connect(sConnect,(SOCKADDR*)&sAddr,sizeof(sAddr)))
{
fprintf(stderr,"无法连接。\nWSAGetLastError=%u\n",WSAGetLastError());
closesocket(sConnect);
continue;
}
if(!pDir)
pDir="";
sprintf(szBuf,
"GET /%s HTTP/1.1\n"
"Accept: text/html, application/xhtml+xml, */*\n"
"Accept-Language: zh-CN\n"
"User-Agent: Mozilla/5.0 (Windows NT 6.1; Trident/7.0; rv:11.0) like Gecko\n"
//"Accept-Encoding: gzip, deflate\n"
"Host: %s\n"
"DNT: 1\n"
"Connection: Keep-Alive\n"
"\n"
,pDir,szUrl+7);
nSendLen=strlen(szBuf)+1;
if(send(sConnect,szBuf,nSendLen,0)!=nSendLen)
fprintf(stderr,"不能完整发送数据包。\nWSAGetLastError=%u\n",WSAGetLastError());
do
{
memset(szBuf,0,BUF_SIZE);
nRecv=recv(sConnect,szBuf,BUF_SIZE,0);
if(nRecv==SOCKET_ERROR)
nRecv=BUF_SIZE;
fwrite(szBuf,1,nRecv,stdout);
printf("%s",szBuf);
}while(nRecv==BUF_SIZE);
puts("\n");
closesocket(sConnect);
}
WSACleanup();
return 0;
BadEnd:
WSACleanup();
return 1;
} 复制代码 代码下载地址: