CSocket (转)

转自:http://blog.sina.com.cn/s/blog_70c2b75601014zts.html 

CSocket socket相关方法 知识点

分类: VC2012-04-07 17:18 32人阅读 评论(0) 收藏 举报

问题一:

自己做了个自定义控件,在里面使用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()...该类型的函数有:     htons()  "Host to Network Short" //将short类型由本机字节顺序转换成网络字节顺序   htonl()  "Host to Network Long"  //将long类型由本机字节顺序转换成网络字节顺序   ntohs()  "Network to Host Short" //将short类型由网络字节顺序转换成本机字节顺序   ntohl()  "Network to Host Long"  //将long类型由网络字节顺序转换成本机字节顺序 记住:这里是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是没有意义的,然而事实不是这样。

    一个插口有几个属性,其中包括协议,本地地址/端口,目的地址/端口。

    对于UDP来说:

    socket()函数建立一个插口;

    bind()函数指明了本地地址/端口(包括ADDR_ANY, 通配所有本地网络接口);

    connect()可以用来指明目的地址/端口;

    一般来说,UDP客户端在建立了插口后会直接用sendto()函数发送数据,需要在sendto()函数的参数里指明目的地址/端口。如果一个UDP客户端在建立了插口后首先用connect()函数指明了目的地址/端口,然后也可以用send函数发送数据,因为此时send函数已经知道对方地址/端口,用getsockname()也可以得到这个信息。

    UDP客户端在建立了插口后会直接用sendto()函数发送数据,还隐含了一个操作,那就是在发送数据之前,UDP会首先为该插口选择一个独立的UDP端口(在1024—5000之间),将该插口置为已绑定状态。如果一个UDP客户端建立插口后首先用bind()函数指明了本地地址/端口,也是可以的,这样可以强迫UDP使用指定的端口发送数据。(事实上,UDP无所谓服务器和客户端,这里的界限已经模糊了。)

    UDP服务器也可以使用connect(),如上面所述,connect()可以用来指明目的地址/端口;这将导致服务器只接受特定一个主机的请求。

    方法一:

    socket()——>sendto()或recvfrom()

    方法二:

    socket()——>connect()——>send或recv()

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;                 //地址的类型.WINDOWS中通常为AF_INET    int h_length;                   //地址的长度.以位来计算(不是以字节来计算)    char **h_addr_list;             //零字节 主机网络地址指针.(网络字节顺序) #define h_addr  h_addr_list[0]       //h_addr_list中的第一地址 }; 14.关于阻塞与非阻塞. 阻塞. 简单的说阻塞就是当你调用accept(),send()或者是recv()等过程后.程序必须要等待一个返回结果程序才会继续执行下去.在这种情况下,如果你的程序是单线程的.那么你的程序就会完全挂起. 非阻塞. 而非阻塞正好相反.非阻塞套接字是指执行此套接字的网络调用时,不管是否执行成功,都立即返回. 习惯上也称之为同步阻塞和异步非阻塞. 在默认情况下套接字都是阻塞模式的.如果要改变这个设置就要调用fcntl()函数. fcntl()定义如下: #include <fcntl.h> int   fcntl(int  s,int  cmd,  ...); 参数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 {         u_int   fd_count;                       SOCKET  fd_array[FD_SETSIZE];   }; 对这些集合的操作都是通过一些特定的宏来完成的.     FD_ZERO(fd_set *set)          //清除一个文件描述符集合       FD_SET(int fd, fd_set *set)   //把fd添加到集合set中       FD_CLR(int fd, fd_set *set)   //从集合set中移去fd       FD_ISSET(int fd, fd_set *set) //测试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; //创建一个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();

}

注意:

        以上文件工程都为空Win32 Console Application;

        需要链接库文件ws2_32.lib,VS2008下链接方法:Project--->Properties---->Linker---->Input---->Additional Dependence项输入ws2_32.lib即可;

        客户端和服务器端程序各自独立编译运行好后,应先运行服务器端,再运行客户端;

        在VS2008中,一个工作区中含有多个工程时,编译运行某个工程的方法是在Solution Explorer窗口中右击某工程的标题,在弹出的菜单中选择Set as Startup Project即可。可以看到,加粗表示的那个工程即是当前活动工程。

posted @ 2014-08-28 15:06  leanee  阅读(83)  评论(0)    收藏  举报