原帖 地址 : http://blog.163.com/zs_note/blog/static/199386112201211103841385/
server端的建立
什么server服务器的建立是socket编程中的基础等等一些废话我就不说了,直接开门见山,进入教程.
首先, 要运行socket程序,就必须先启动windows socket服务,如下代码:
WSADATA wsaData; WSAStartup( 0x101 , &wsaData);//服务的启动函数 //服务的细节保存在wsaData数据中. ..... WSACleanup();
WSAStartup() 是个启动socket服务的函数,在程序的开头调用.
其中第一个参数是指服务版本,占两个字节,高字节指定副版本号,低字节指定主版本号.
wsaData 是用来保存socket服务启动的相关细节.
WSACleanup() 是在退出程序前调用,用来关闭socket服务,释放内存.
启动完服务后, 先建立一个网络通信描述符,如下代码:
SOCKET serverSock; serverSock = socket( AF_INET , SOCK_STREAM , IPPROTO_TCP ); if( serverSock == INVALID_SOCKET ) { puts("socket() error"); printf("错误代码:%d \n" , WSAGetLastError()); exit(1); }
socket函数的第一个参数指定s网络通信的地址类型.在winsock2.h头文件中,可以看到他们是一堆”AF_”开头的宏定义.因为我们的通信是建立在IPV4上,所以用AF_INET,如果要IPV6,就换成AF_INET6.
第二个参数是流类型.SOCK_STREAM表示流的类型为数据流.如果是SOCK_RAM,则为原始流.数据流应用于TCP协议的连接,TCP协议把从 网络层里得到的数据包一个个解开,然后将其中的数据按顺序拼接,返回给用户,所以用户看到的是数据是流的形式.如果采用原始流,则用户得到的数据将是一包 包的原始IP数据包,需要用户自己去解开,才得到其中的数据.
第三个参数是协议类型,例子中的协议类型为TCP.如果流类型选择SOCK_RAM,则协议类型就要改为IPPROTO_IP,如果流类型选择SOCK_DGRAM(UDP数据包流),则协议类型改为IPPROTO_UDP.
以上三个参数的值,都在头文件winsock2.h中有定义,通过文件搜索自行查看.
当socket()函数运行正确,则返回网络通信描述符,如果失败,返回INVALID_SOCKET.
在socket网络编程中,如果库函数运行失败,则可以用WSAGetLastError()来获得错误代码.
有了描述符后, 继续建立一个IP结构体数据,如下代码:
struct sockaddr_in serverAddr; memset(&serverAddr , 0 , sizeof(struct sockaddr_in) );//初始化为0; serverAddr.sin_family = AF_INET;//地址簇 serverAddr.sin_port = htons( 80 );//端口号 serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");//IP地址
在设置地址信息前,记得将serverAddr中的内存初始化为0;
sin_family 即地址簇.
sin_port 即端口号.函数htons(80),把主机字节序保存的值为”80″的short型端口号,转换为网络字节序的端口号.
sin_addr.s_addr 是以unsigned long保存的4字节IP地址.而inet_addr(“127.0.0.1″)函数是把点隔字符串类型的IP地址转换为unsigned long的IP地址.
IP数据埴完后,把IP结构体信息和socket网络通信描述符绑定在一起,如下代码:
if( bind(serverSock , (struct sockaddr*)&serverAddr , sizeof(struct sockaddr_in)) == SOCKET_ERROR ) { puts("bind() error"); exit(1); }
bind()函数中的
第一参数是要绑定且还未绑定的描述符.
第二参数是要绑定的IP结构体指针.(记得把指针强行转换为struct sockaddr*类型,struct sockaddr_in 结构体类型是从原始地址结构体struct sockaddr继承来的 ,两者在有的时候可以通用,有的时候则不能.具体以后再讲).
第三参数就是结构体指针的数据大小.
如果函数发生错误,则返回SOCKET_ERROR.
绑定成功后,就开始监听指定地址上的指定端口上的连接信息,代码如下:
if( listen(serverSock , 10) == SOCKET_ERROR) { puts("listen() error"); exit(1); }
listen()函数;
第一个参数接收的是已经绑定了IP地址信息的通信描述字.
第二个参数是表示充许请求连接的列队的长度,现在把它设置为10;
函数失败,返回SOCKET_ERROR.
然后,程序就会在相关地址的相关端口上监听有关来自client的连接信息.
既然有等待连接,那么server是不是有个什么函数来处理连接呢.
有,看下面:
SOCKET connectingSock;//用来保存客户连接进来的描述符 struct sockaddr_in connectingAddr;//用来保存客户的地址数据 int addrlen;//用来保存客户地址数据的长度 connectingSock = accept( serverSock , (struct sockaddr*)&connectingAddr , &addrlen); if( connectingSock == INVADIL_SOCKET ) { puts("accept error"); exit(1); }
在上面代码中:
accept 是用来处理连接到server的client.如果处理成功, 则返回一个客户连接的描述符,connectingSock就是用来接收这个描述符.这个描述符可以用来传输数据用.
connectingAddr 以参数形式传递给accept函数, 是用来接收来自客户的地址信息. 并接收地址信息的长度,保存在addrlen变量中.
serverSock 必须是已经处于监听状态的通信描述符.则否会处理失败.
如果accept运行失败,则返回INVALID_SOCKET.
需要注意的是accept是个阻塞函数, 也就是说,如果没有client请求连接,则函数一直不能返回,程序就一直在监听端口,直到有client连接上来为止,函数才会返回,程序才会继续运行.
server与client的连接已经处理完成,现在开始传输数据.
当程序一进入连接,我们的server先接收来自客户端的消息 ,然后等待server的回信,具体代码如下:
char msg[1000];//保存消息用 memset( msg , 0 , 1000);//初始化为0 if( recv( connectingSock , msg , 999 , 0 ) <= 0) { puts("recv() msg error"); } else { puts( msg );//把消息打印出来 } memset( msg , 0 , 1000) ; sprintf(msg , "Hello client ! "); if( send( connectingSock , msg , strlen(msg) , 0 ) <= 0 ) { puts("send() msg error"); }
上面代码中, recv是接收信息函数:
第一个参数,是已经连接的通信描述符.
第二个参数,是接收消息的地址.
第三个参数,是最多接收多少信息,是为了防止内存溢出.
第四个参数,暂时不在讨论范围内,基本上都设置为0,以后有碰到再讲解.
如果有接收到信息,则函数返回接收的字节数.
如果连接失败,或断开连接,返回0.
如果函数运行错误,则返回-1;
接收到信息后,把信息打印出来.
send是个发送信息的函数.
函数中,msg表示等待发送的信息的地址.
strlen(msg) 表示的是要发送的字节数.
函数返回发送的字节数.如果断开连接,返回0,如果函数错误,返回-1.
这里还要特别说明,recv 和 send 根accept一样,都是阻塞函数.也就是说,在recv函数中,如果没有信息可以接收,则程序也会一直等.但是有一点不一样,如果与client的连接 断开,函数反回0 . accept则是一辈子等下去.send函数的情况和recv一样.
这样,我们的server的核心内容都已经展现出来了,接下去就是关闭socket,和释放内存了:
closesocket(connectingSock); closesocket(serverSock); WSACleanup();
在最后,我把这章的server服务端的全部代码整理如下:
/* server.c 请直接将本代码复制粘贴到你的编译环境中,编译且运行. 运行后,可在浏览器中输入地址http://127.0.0.1/来访问server. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <WinSock2.h> #pragma comment(lib , "Ws2_32.lib") int main( int argc , char* argv[]) { char msg[1000]; SOCKET serverSock , connectingSock; struct sockaddr_in serverAddr , connectingAddr; int addrlen ; WSADATA wsaData; if( WSAStartup( 0x101 , &wsaData) != 0) { puts("socket服务启动失败"); exit(1); } serverSock = socket( AF_INET , SOCK_STREAM , IPPROTO_TCP); if( serverSock == INVALID_SOCKET ) { puts("socket建立失败"); exit(1); } addrlen = sizeof(struct sockaddr); memset( &serverAddr , 0 , addrlen); serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(80); serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); if( bind( serverSock , (struct sockaddr*)&serverAddr , sizeof(struct sockaddr_in) ) == SOCKET_ERROR ) { puts("绑定IP 地址到socket失败"); exit(1); } if( listen( serverSock , 10 ) == SOCKET_ERROR ) { puts("设置socket为监听状态失败"); exit(1); } while(1){ puts("等待客户端连接..."); connectingSock = accept( serverSock , (struct sockaddr*)&connectingAddr , &addrlen); if( connectingSock == INVALID_SOCKET ) { puts("处理连接时发生错误"); break; } memset( msg , 0 , 1000) ; if ( recv( connectingSock , msg , 999 , 0) <= 0 ) { puts("接收消息失败"); } else { puts( msg); } sprintf( msg , " Hello client ! "); if( send( connectingSock , msg, strlen(msg ) , 0) <=0 ) { puts("发送消息失败"); } else { puts("发送消息成功"); } closesocket(connectingSock); } closesocket(serverSock); WSACleanup(); return 0; }
摸索学习中,与诸君共勉。