c语言的socket编程----服务器端

原帖 地址  :   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;
 }

摸索学习中,与诸君共勉。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值