问题一:
自己做了个自定义控件,在里面使用CSocket进行连接,对该控件进行调试的时候发送和接受都是好用的,但是当把控件嵌入到一个应用中时,发送OK,但是却接受不到东西
实验证明,要把这个CSOCKT的创建及发送都放在应用的主线程中就是可以接受到数据了,这是问什么呢?
问题二:
关于使用socket接口的tcp与udp连接:
(文章末尾有实例代码)
3.sockaddr的定义:
struct sockaddr
{
unsigned short sa_family;
char sa_data[14];
};
sa_family 能够是各种各样的类型,但是在这篇文章中都是 "AF_INET".
sa_data包含套接字中的目标地址和端口信息.
为了处理sockaddr程序员创造一个并列的结构sockaddr_in后面的in表示"Internet"它的定义如下:
struct sockaddr_in
{
short int sin_family;
unsigned short int sin_port;
struct in_addr sin_addr;
unsigned char sin_zero[8];
};
用sockaddr_in结构可以轻松的处理套接字地址的基本元素.注意:sin_zero的作用是为了与结构体sockaddr保持相同的长度.在使用的时候应用memset()类型的函数来将它全部置0.正是有了它.你才可以在使用sockaddr的地方仍然使用sockaddr_in结构代替.只需要做简单的转换即可.
同时注意sockaddr_in中的sin_family和sockaddr中的sa_family一致.
最后sin_port和sin_addr必须是网络字节顺序 (Network Byte Order)
4.关于字节顺序.
事实上有两种字节排列顺序:A.重要的字节在前.B.不重要的字节在前面.
而前一种字节排列顺序就叫网络字节顺序.
当我们说某一数据必须按照网络字节顺序.那么你就需要调用函数(例如:htons())来将它从本机字节顺序转换成网络字节顺序.我们能够转换两种类型"short"和"long"对于unsigned也实用
假如:你想将short从本机字节顺序转换成网络字节顺序.用"h"表示本机(host),接着是"to"然后用"n"表示网络(network)最后用"s"表示short类型,这样就可以得到这个函数htons()...该类型的函数有:
htonl()
ntohs()
ntohl()
记住:这里是Unix的世界.在你将数据放到网络上的时候,请确信它们是按网络字节顺序排列的.
为什么在sockaddr_in结构中sin_addr和sin_port需要转换成网络字节顺序.而sin_family不需要呢?
答案是:sin_addr和sin_port分别封装在包的IP层和UDP层.因此它必须按网络字节顺序.但是sin_family只是被内核用来判断数据结构中包含什么类型的地址.它并没有被放到网络上进行传送.所以它可以是本机字节顺序.
5.IP地址及其处理方法
假设你现在已经创建了一个sockaddr_in结构体myaddr 现在你要将IP地址"192.168.1.110"存贮到结构体中.你只需要调用函数inet_addr()将IP地址从点数格式转换成无符号长整型.例:
myaddr.sin_addr.s_addr = inet_addr("192.168.1.110");
需要注意的是:函数inet_addr()返回的已经是网络字节顺序.所以你无需再调用htonl()函数来进行转换.
还需要注意一点:函数inet_addr()在错误时返回值为-1.刚好等于IP地址"255.255.255.255"这可是个广播地址.所以在使用inet_addr()函数时一定要进行错误检查.
函数inet_ntoa()则刚好相反,它可以将一个结构体in_addr输出成点数格式的IP地址.
注意:inet_ntoa()函数将结构体in_addr作为一个参数.而不是长整型.
6.socket()函数
功能:建立一个套接字
定义:int socket(int domain, int type, int protocol);
参数domain指定通信发生的区域,windows中只支持"AF_INET"
参数type描述要建立的套接字的类型.如SOCK_STREAM类型或SOCK_DGRAM类型
参数protocol说明该套接字使用的特定协议.如果调用者不希望特别指定使用的协议,则置为0,使用默认的连接模式
函数socket()将会根据指定的三个参数建立一个套接字.并将相应的资源分配给它.同时返回一个整型套接字描述符.
注意:如果该函数调用失败返回值为-1.
7.bind()函数
功能:将套节字和本地地址关联在一起
定义:int bind(SOCKET s,struct sockaddr *name, int namelen);
参数s是由socket()函数返回的并且未作连接的套接字描述符
参数name是赋给套接字s的本地地址结构的指针.(也就是sockaddr_in结构)
参数namelen表明了本地地址结构的长度.
函数调用成功返回0,有错误则返回-1.
在进行绑定本地地址时有几个注意事项:
A.将sockaddr_in结构的sin_port置为0时,告诉系统随机选择一个没有使用的端口.
B.将sockaddr_in结构的sin_addr.saddr置为INADDR_ANY,告诉它自动填上它所运行的机器的IP地址
C.如果需要手动指定端口.一定记住不要采用小于1024的端口,因为所有小于1024的端口都被系统保留.而我们可以选择的端口则从1024到65535
D.如果你使用connect()来和远程机器通讯,你不需要关心你的本地端口,你只需要简单的调用connect()就可以了,它会检查套接字是否绑字端口,如果没有,它会自己绑定一个没有使用的端口.
8.connect()函数
定义:int connect(SOCKET s,struct sockaddr *name, int namelen);
参数s是欲建立连接的本地套接字描述符.
参数name是将要连接的目标地址结构sockaddr的指针.
参数namelen指明目标地址结构的长度
函数在错误时返回-1.
注意:在使用connect()函数来和远程机器建立连接时可以不需要调用bind()函数,因为我在乎本地端口号.我只关心我要连接到哪里(也就是目标),并且连接后目标会自动获取我们的IP地址,以及我们所使用的端口等信息.
关于UDP SOCKET编程中的connect():
UDP是一个无连接的协议,因此socket函数connect()似乎对UDP是没有意义的,然而事实不是这样。
9.listen()和accept()函数.
假如你不希望与远程的一个地址相连,那么你就需要等待接入请求.并且用各种方法处理它们.这个过程就是听及listen()然后接受accept()
listen()用于监听连接.它地调用需在accept()之前
定义:int listen(SOCKET s, int backlog);
参数s标识一个本地已建立,尚未连接的套接字描述符.
参数backlog表示请求连接队列的最大长度,大多数系统允许最大数目是20.
成功返回0,错误则返回-1.
accept()函数
系统调用accept()的情况就像有一台远程的机器通过你在监听listen()的端口连接connect()到你的机器.这个连接将被加入到等待接受的队列中.然后你调用accept()告诉它你有空闲的连接.这时系统将返回一个新的套接字描术符.这样你就有两个套接字了.原来的一个还在监听那个端口是否有新的连接到来.新的这个套接字则准备发送send()或者接收recv()数据了.
定义:SOCKET accept(SOCKET s, struct sockaddr *addr, int *addrlen);
参数s为本地套接字描述符,也就是正在用于监听listen()的那个套接字描术符.
参数addr是一个struct sockaddr结构指针.它用来存贮客户的地址.也就是要求连接的机器的地址信息.
参数addrlen保存客户方套接字地址的长度(注意它是一个int型指针)
函数调用成功将会返回一个套接字(表示接收到的套接字).如果失败则返回-1.
10.send()和recv()函数
不管是服务端程序或是客户端程序都将使用这两个函数从TCP连接的另一端发送或接收数据.
send()函数
定义:int send(int s, void *msg, int len, int flags);
参数s是你相发送数据的套接字描述符.这里可能是socket()返回的,也可能是accept()返回的.
参数msg是你想要发送的数据的指针.
参数len是将要发送的数据的长度.
参数flags通常设为0.
如果将要发送的数据的长度len大于套接字s的发送缓冲区的长度.该函数将返回-1.
如果len小于或等于s的发送缓冲区长度,那么send()先检果协议是否正在发送s的发送缓冲区中的数据.如果是就等待协议把数据发送完.如果协议还没有开始发送s的发送缓冲区中的数据或者s的发送缓冲区中没有数据,那么send()就比较发送缓冲区和剩余空间和len,如果len大于s的发送缓冲区剩余空间send()就会一直等待协议把s的发送缓冲区中的数据发送完.如果len小于发送缓冲区的剩余空间send()就把msg所指的数据拷贝到s的发送缓冲区剩余空间里.
注意:并不是send()把数据传送到连接的另一端.而是具体的协议来完成传送的.send()只是把数据拷贝到套接字的发送缓冲区剩余空间里而以.
如果send()拷贝数据成功.就返回实际拷贝数据的字节数.
另外send()把数据拷贝到套接字的发送缓冲区中就返回了.此时.这些数据并不一定马上被传送到连接的另一端.
注意:在Unix系统下.如果send()在等待协议传送数据时网络断开的话,调用send的进程会接收到一个SIGPIPE信号,进程对该信号的默认处理是进程终止.
recv()函数
定义:int recv(int s, void *buf, int len, int flags);
参数s为接收端的套接字描述符.
参数buf指明一个缓冲区.该缓冲区用来存放recv()接收到的数据.
参数len指明缓冲区的长度.
参数flags通常设为0.
成功将返回实际写入缓冲区的数据的长度.失败返回-1.
同样的.recv()函数只是负责把数据从s的接收缓冲区中拷贝到buf缓冲区中.真正的接收数据的工作是由协议来完成的.所以协议接收到的数据有可能大于buf缓冲区的长度.在这种情况下要调用多次recv()函数才能把接收缓冲区中的数据拷贝完.
另外:除了send()函数外其它的socket函数比如recv()都要先等待套接字的发送缓冲中的数据被协议传送完毕才能执行.
11.sendto()和recvfrom()函数
Sendto()和recvfrom()函数用于在无连接的数据报套接字方式下进行数据发送和接收.
sendto()函数
定义:int sendto(int s,void *msg,int len,unsigned int flags,struct sockaddr *to, int tolen);
在发送数据时.由于本地端并没有与远程机器建立一个连接.所以在用sendto()发送数据时我们要告诉它把数据发送到哪里?也就是告诉sendto()发送数据的目的地.
所以它比send()函数多了两个参数.
参数 "struct sockaddr* to"用来告诉它发送数据目的地.包括目标机器的IP和端口等信息.
参数 "int tolen"是struct sockaddr结构的大小.
除此之外其它的参数与send()函数完全相同.
函数成功返回实际发送数据的长度.失败返回-1.
recvfrom()函数
定义:int recvfrom(int s,void *buf,int len,unsigned int flags,sockaddr*from,int *fromlen);
因为是利用数据报套接字传送的数据.所以并没有连接.所以recvfrom()在收到数据后需要知道数据到底是谁发来的?在这里称为"源".所以它比recv()函数多了两个参数.
参数from用来记录源机器的地址信息.包括IP地址和端口等信息.
参数fromlen同样是struct sockaddr结构的大小.
其它参数与recv()函数完全相同.
函数成功时返回实际收到的数据长度.失败返回-1.
12.套接字的关闭.
当我们完成通讯后我们需要把套接字关闭.从而释放资源.
关闭套接字的操作通过调用close()函数来实现.
不管是有连接的流式套接字还是没有连接的数据包式的套接字.只要任何一端调用了close()函数关闭了套接字.都将无法再进行通讯.
另外还有一个函数就是shutdown().它允许你将一定方向上的通讯关闭.当然也可双向通讯都关闭.
也就是说shutdown()函数可以让某一端只接收数据.或者只发送数据.
定义:int shutdown(int s,int how);
参数s是我们将要操作的套接字.
参数how是具体的操作方式.
A.为0时表示不允许接收数据
B.为1时表示不允许发送数据
C.为2时表示不允许接收和发送数据(双向通讯都将关闭,与调用close()函数效果一样.)
函数成功时返回0失败时返回-1.
13.其它.
getpeername()函数.
功能:它用来在有连接的流式套接字中获取对方的地址信息.(包括IP地址和端口)
定义:int getpeername(int s, struct sockaddr *addr, int *addrlen);
参数addr用来存放获取的地址信息.
函数失败返回-1.
gethostname()函数.
功能:它用来获取本机的名字.
定义:int gethostname(char *hostname, size_t len);
参数hostname是一个字符类型的缓冲区.它用来保存本机名字
参数len是缓冲区的长度.
函数成功时返回0.失败时返回-1.
gethostbyname(char*name)函数.
功能:返回由参数name指定的机器的地址信息
定义:struct hostent *gethostbyname(const char *name);
参数name可以是用gethostname()函数获取的机器名字
函数成功返回一个指向struct hostent结构体的指针.失败返回NULL
另:此函数返回的是一个struct hostent结构指针.该结构就包含了一个机器的地址信息
该结构的定义:
struct hostent
{
char *h_name;
char **h_aliases;
int h_addrtype;
int h_length;
char **h_addr_list;
#define h_addr
};
14.关于阻塞与非阻塞.
阻塞.
简单的说阻塞就是当你调用accept(),send()或者是recv()等过程后.程序必须要等待一个返回结果程序才会继续执行下去.在这种情况下,如果你的程序是单线程的.那么你的程序就会完全挂起.
非阻塞.
而非阻塞正好相反.非阻塞套接字是指执行此套接字的网络调用时,不管是否执行成功,都立即返回.
习惯上也称之为同步阻塞和异步非阻塞.
在默认情况下套接字都是阻塞模式的.如果要改变这个设置就要调用fcntl()函数.
fcntl()定义如下:
#include <fcntl.h>
int
参数s是我们将要设置的套接字.
参数cmd有多种形式.在这里一般设置为F_SETFL.
第三个参数总是一个整数.在此处只需要设置成O_NONBLOCK(表示非阻塞模式)
注意:在windows下的socket编程中.是用iooctlsocket()函数来实现fcntl()函数的功能的.
它的定义如下:
int ioctlsocket(int s,long cmd,u_long *argp);
功能:控制套接字的模式.
参数s是将要操作的套接字.
参数cmd是对套接字的操作方式.
参数argp指向cmd命令所带参数的指针.
参数cmd支持下列几种命令:
A.FIONBIO允许或禁止套接字s的非阻塞模式.参数argp指向一个无符号长整型.如果要允许非阻塞模式则将参数argp设成非零.如果要禁止非阻塞模式(也就是阻塞模式)则将参数argp设成零即可.
注:一个套接字进行了WSAAsyncSelect()操作.它是套接字的一种模式,它将套接字自动设置成非阻塞模式.则任何用试图ioctlsocket()来重新设置套接成阻塞模式的操作都将失败.必须先调用WSAAsyncSelect()来禁止WSAAsyncSelect后才可以.关于WSAAsyncSelect()请查阅其它资料.
B.FIONREAD确定套接字s自动读入数据量.参数argp此时用来存放ioctlsocket()的返回值.如果套接字是SOCKET_STREAM类型(即流式套接字)则FIONREAD返回一次在recv()中所接收到的所有数据量.(即数据长度).这通常与套接字中排队的数据总量相同.如果套接字是SOCK_DGRAM类型.则FIONREAD返回套接字上排队的第一个数据报的大小.
还有一些其它的命令.略.
函数调用成功返回0.失败返回-1.
从系统性能上看,用非阻塞的socket效率和性能更高,但是编程更复杂,特别是当你使用事件或者消息的时候,但是,你可以通过多个工作线程管理多个socket连接,效率非常高,不需要每个工作线程只管理一个socket连接. 用阻塞的方式比较简单,但当较多客户端时消耗系统资源太多.
其大概思想是建立一个线程池,当有socket的事件需要处理时,从线程池中取一个线程来执行,执行完,将线程归还到线程池中.
每个socket连接的时间较长,不断的与服务器交互.每个连接的socket并不是每时每刻都在收发数据.收发数据是断断续续的.这种情况就很适合于上面的方法.
简单的讲同步阻塞常用于连接较少流量较大的地方.非阻塞则刚好相反.用于连接较多.流量较小的地方.
如果要采用阻塞模式.并且要同时处理多个连接.就只能运用多线程来处理.
多路同步.
假设有一台服务器.它一边要进行监听是否有连接到来,还要在其它已经完成的连接上接收数据.也就是accept()和recv()两个函数的调用.当你在调用accept()时遇到阻塞该怎么办.前提是你并没有调用
fcntl()函数把套接字设置非阻塞模式.这时你就可以调用select().它让你可以同时监视多个套接字.它可以告诉你.哪个套接字准备读,哪个套接字准写,哪个套接字发生了意外.
select()函数的定义如下:
int select(int nums,fd_set*reads, fd_set*writes,fd_set *excepts,timeval *timeout);
在讲解select()函数之前我们先讲一下fd_set.
它表示一个集合.具体的讲是一个文件描述符的集合.在此例中都把它理解为套接字描述符.
在Unix中一切都是文件.包括输入设备键盘.硬盘上的文件.还有专门用于网络传输的socket(套接字)等等.所以.在这里都把它当作是socket套接字.
那么文件描述符或者是套接字描述符就好比是WINDOWS下面的句柄.
fd_set的定义如下:
struct fd_set
{
};
对这些集合的操作都是通过一些特定的宏来完成的.
现在再来说说select()函数,以及它的用法.
该函数的功能就是用来等待套接字(这里只讨论套接字)状态的改变.
参数nums代表最大的套接字描述符加1.
参数reads,writes和excepts是套接字描述符集合,用来回传该套接字描述符的读,写或例外的状况.
参数timeout指向一个timeval结构.它用来设定select等待的时间.可以设置到微秒级.1秒等于1000毫秒.1毫秒等于1000微秒.虽然可以精确到1微秒.但这只是理想的.实际上跟系统还有硬件有关.
timeval结构的定义如下:
struct timeval
{
int tv_sec;
int tv_usec;
};
当然你可以将参数timeout设置成NULL,那么它将永远不会超时.
成功返回套接字描述状态改变的个数,如果返回0表示在套接字描述符状态改变前已经超过参数timeout设定的时间.当有错误发生时则返回-1.此时参数reads,writes,execepts和timeout的值变得不可预测.
同时有错误发生时,它会设置全局错误变量.常见错误有下面几种.
EBADF 套接字描述符无效或该套接字已关闭
EINTR 此调用被信号所中断
EINVAL 参数n 为负值
ENOMEM 核心内存不足
select()函数的常见用法.
//============
sockfd = socket(AF_INET, SOCK_STREAM, 0); //获取一个套接字
fs_set readset; //定义套接字集合,用于查看读状态
FD_ZERO(&readset); //对该集合进行清除操作.
timeval
out.tv_sec=5; //设定等待秒数为5秒
out.tv_usec=1000*500; //设定等毫秒数为500毫秒
//总的等待时间为5秒+500毫秒.
select(sockfd+1,&readset,NULL,NULL,&out); //select()函数的调用.
if(FD_ISSET(sockfd,readset))//进行判断
//=============
15.socket编程的最后一个问题
这也是socket编程中的第一个问题.如果你是在windows下面编程的话.
在windows下面进行socket编程我们首先要对socket进行初始化.这时就要用到WSAStartup()函数.
在windows中要调用所有的socket函数之前.我们必须先调用WSAStartup()函数
其定义如下:
int WSAStartup(WORD wVer,WSADATA* lpWSAData);
参数wVer指明可使用的windows socket的版本号.高位字节标明副版本号.低位字字指明主版本号.
参数lpWSAData是指向WSADATA结构的指针.用来接收windows socket实现的细节.
成功时返回0失败返回错误代码.
这里将用MAKEWORD()宏.
MAKEWORD(a,b)它的作用是把a和b组成一个WORD值.
其中第一个参数a是高8位的值.第二个参数b是低8位的值.
我们就是用它来把windows socket的版本号放入WSAStartup()函数的.
例程:
//=====================
WORD sver=MAKEWORD(1,1);//设置socket版本号为1.1
WSADATA wsda;//创建WSADATA结构
if(WSAStartup(sver,&wsda)==0)//调用WSAStartup函数初始化socket
{
}
//=====================
另外一个函数.它是windows socket编程中的最后一个问题.
WSACleanup()函数.它是终止windows socket的调用.注销windows socket.释放资源.
任何打开的并已建立连接的SOCK_STREAM流式套接字.在调用WSACleanup()时会重置.而已经由close()关闭却仍有要发送而未发送的数据的套接字则不会受影响.该数据仍要发送.
通常.WSACleanup()函数与WSAStartup()函数应成对出现.也就是说对于一个进程的每一次WSAStartup()调用.必须有一个WSACleanup()调用.
但是只有最后一个WSACleanup()调用才会做实际的清除工作.前面的WSACleanup()调用仅仅将windows socket DLL中的内置引用计数递减1.
一般情况下,为了确保WSACleanup()调用了足够的次数.可以在最后用一个循环来不断的调用WSACleanup()函数.直到返回WSANOTINITIALISED为止.
该函数的定义:
int WSACleanup ( void );
函数成功返回0.失败返回-1.同时会设置全局错误变量.
常见的几种错误代码.
A.WSANOTINITIALISED使用本函数之前没有一次成功的WSAStartup()调用.简单的讲.它就是说.前面已经没有WSAStartup()调用了.在这里可以不需要再调用WSACleanup()函数来释放资源了.
B.WSAEINPROGRESS一个阻塞的windows socket操作正在进行.简单的讲.它就是说你正准备注销的这个套接字中某一个操作(比如recv())遇到了阻塞.
Win32下Udp、Tcp程序示例:
一、Udp程序
服务器端UdpSrv.cpp
#include <stdio.h>
#include <WinSock2.h>
void main()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested=MAKEWORD(1,1);
err=WSAStartup(wVersionRequested,&wsaData);
if(err)
return;
if(LOBYTE(wsaData.wVersion)!=1||HIBYTE(wsaData.wVersion!=1))
{
WSACleanup();
return;
}
SOCKET sockSrv=socket(AF_INET,SOCK_DGRAM,0);
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(6000);
int addrSrvLen=sizeof(SOCKADDR);
bind(sockSrv,(SOCKADDR*)&addrSrv,addrSrvLen);
SOCKADDR_IN addrClient;
char recvBuf[100];
int addrClientLen=sizeof(SOCKADDR);
recvfrom(sockSrv,recvBuf,100,0,(SOCKADDR*)&addrClient,&addrClientLen);
printf("%s said:%s",inet_ntoa(addrClient.sin_addr),recvBuf);
closesocket(sockSrv);
WSACleanup();
getchar();
}
客户端UdpClient.cpp
#include <stdio.h>
#include <WinSock2.h>
void main()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested=MAKEWORD(1,1);
err=WSAStartup(wVersionRequested,&wsaData);
if(err)
return;
if(LOBYTE(wsaData.wVersion)!=1||HIBYTE(wsaData.wVersion!=1))
{
WSACleanup();
return;
}
SOCKET sockClient=socket(AF_INET,SOCK_DGRAM,0);
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(6000);
int addrSrvLen=sizeof(SOCKADDR);
sendto(sockClient,"Hellow!",strlen("Hellow!")+1,0,(SOCKADDR*)&addrSrv,addrSrvLen);
closesocket(sockClient);
WSACleanup();
}
二、Tcp程序
服务器端TcpSrv.cpp
#include <WinSock2.h>
#include <stdio.h>
//#pragma comment (lib, "Ws2_32.lib") //引入库Ws2_32.lib
void main()
{
WORD wVersionRequested;//保存需要请求的winsock库版本号
WSADATA wsaData;//用以保存推荐的winsock库信息
int err;
wVersionRequested=MAKEWORD(1,1);//调用MAKEWORD宏创建一个包含请求版本号的WORD值,高字节1表示副版本号
err=WSAStartup(wVersionRequested,&wsaData);//加载套接字库
if (err!=0)//如果请求的版本比比最低版本低或者加载套接字库失败,返回
return;
if (LOBYTE(wsaData.wVersion)!=1||HIBYTE(wsaData.wVersion)!=1)//如果推荐版本和请求版本不同,返回
{
WSACleanup();//终止对winsock库使用
return;
}
//创建用于监听的套接字
SOCKET sockSrv=socket(AF_INET,SOCK_STREAM,0);
SOCKADDR_IN addrSrv;//用于保存本地地址和指定的端口
//注意SOCKADDR_IN结构体除sin_family外其他都是按网络字节顺序表示
addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);//本地地址,任意地址,转换为网络字节顺序
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(6000);//端口,必须1024以上,转换为网络字节顺序
//将套接字和本地地址和指定端口绑定
bind(sockSrv,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));
//将套接字设为监听模式,准备接收客户请求
listen(sockSrv,5);//等待连接队列的最大长度设为5
SOCKADDR_IN addrClient;//用于接收客户端地址信息
int len=sizeof(SOCKADDR);//在调用accept函数前必须给第三个参数赋一个初始值
while (1)
{
//等待客户请求到来
SOCKET sockConn=accept(sockSrv,(SOCKADDR*)&addrClient,&len);
char sendBuf[100];//
sprintf(sendBuf,"Welcome %s to http://www.sunxin.org" ,inet_ntoa(addrClient.sin_addr));
//向客户端发送数据
send(sockConn,sendBuf,strlen(sendBuf)+1,0);
char recvBuf[100];
//接收数据
recv(sockConn,recvBuf,100,0);
//打印接收的数据
printf("%s\n",recvBuf);
//关闭套接字
closesocket(sockConn);
}
//如果不是死循环还要关闭套接字,释放资源
//closesocket(sockSrv);
//WSACleanup();
}
客户端TcpClient.cpp
#include <WinSock2.h>
#include <stdio.h>
void main()
{
//加载套接字库
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested=MAKEWORD(1,1);
err=WSAStartup(wVersionRequested,&wsaData);
if (err)
return;
if(LOBYTE(wsaData.wVersion)!=1||HIBYTE(wsaData.wVersion)!=1)
{
WSACleanup();
return;
}
//创建套接字
SOCKET sockClient=socket(AF_INET,SOCK_STREAM,0);
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");//服务器网络地址
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(6000);
//向服务器发出连接请求
connect(sockClient,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));
//接收数据
char recvBuf[100];
recv(sockClient,recvBuf,100,0);
printf("%s\n",recvBuf);
//发送数据
send(sockClient,"This is lisi",strlen("This is lisi")+1,0);
//关闭套接字
closesocket(sockClient);
WSACleanup();
getchar();
}
注意: