socket编程(模拟服务器和网页)

简介:

用socket模拟一个服务器,然后再写一段程序用于模拟一个网页,拿这个模拟的网页去访问服务器,然后服务器接收到网页的基本信息,再返回给网页已收到的信息。

知识准备:

TCP/IP编程模型:

server        client

socket        socket

bind          

listen        connect

accept

send        recv

recv        send

可能用到的函数简介:

创建socket
初始化socket
struct sockaddr_in {
short int sin_family;                      /* Address family */
unsigned short int sin_port;       /* Port number */
struct in_addr sin_addr;              /* Internet address */
unsigned char sin_zero[8];         /* Same size as struct sockaddr */
};
【socket】
向用户提供一个套接字
包含的头文件#include <sys/socket.h>
函数原型 int socket(int family,int type,int protocol)
family:指定一个协议簇AF_INET指定为IPv4协议,AF_INET6指定为IPv6,AF_LOCAL指定为unix协议域。协议簇是网络层的协议
type:指定一个套接口的类型。套接口类型有:SOCK_STREAM字节流、SOCK_DGRAM数据报、SOCK_SEQPACKET有序分组、SOCK_RAW原始套接口.
protocol指定相应的传输协议。TCP,UDP,SCTP要指定它们分别使用的宏IPPROTO_TCP、IPPROTO_UPD、IPPROTO_SCTP
返回值:成功返回一个套接字,出错返回一个-1.
区分一个概念:协议和协议簇
IPv4可以用来实现TCP或UDP等传输层协议,所以称为协议簇,相应的传输层的协议就简单的称为协议
【bind】
包含的头文件#include <sys/socket.h>
函数原型int bind(int   sockfd,  const  struct  sockaddr  *my_addr,  socklen_t addrlen);
sockfd:套接字描述词。
my_addr:需要绑定在套接字上的地址,是类似于以下结构体的变量
struct sockaddr {
    sa_family_t sa_family;/*address family,AF_XXX*/
    char        sa_data[14];/*14 bytes of protocol address*/
}
addrlen:地址的长度,以字节为单位。  
返回值:成功返回0。失败返回-1。
【listen】 
使用主动连接套接口变为被连接套接口,使得一个进程可以接受其它进程的请求,从而成为一个服务器进程。在TCP服务器编程中listen函数把进程变为一个服务器,并指定相应的套接字变为被动连接。
listen函数一般在调用bind之后-调用accept之前调用。
函数原型是:int listen(int sockfd, int backlog)
包含的头文件#include<sys/socket.h>
返回:0──成功, -1──失败
sockfd:由socket函数返回。
backlog:这个参数涉及到一些网络的细节。在进程正理一个一个连接请求的时候,可能还存在其它的连接请求。因为TCP连接是一个过程,所以可能存在一种半连接的状态,有时由于同时尝试连接的用户过多,使得服务器进程无法快速地完成连接请求。如果这个情况出现了,服务器进程希望内核如何处理呢?内核会在自己的进程空间里维护一个队列以跟踪这些完成的连接但服务器进程还没有接手处理或正在进行的连接,这样的一个队列内核不可能让其任意大,所以必须有一个大小的上限。这个backlog告诉内核使用这个数值作为上限。一般这个值会小30以内。
【accept】
等待并接受客户的连接。
它从内核中取出已经建立的客户连接,然后把这个已经建立的连接返回给用户程序,此时用户程序就可以与自己的客户进行点到点的通信了。
accept函数等待并接受客户请求
包含的头文件#include<sys/socket.h>
函数原型int accept(int sockfd, struct sockaddr* addr, socklen_t* len)
sockfd这个套接字用来监听一个端口,当有一个客户与服务器连接时,它使用这个一个端口号,而此时这个端口号正与这个套接字关联。当然客户不知道套接字这些细节,它只知道一个地址和一个端口号。
addr这是一个结果参数,它用来接受一个返回值,这返回值指定客户端的地址,当然这个地址是通过某个地址结构来描述的,用户应该知道这一个什么样的地址结构。如果对客户的地址不感兴趣,那么可以把这个值设置为NULL。
len也是结果的参数,用来接受上述addr的结构的大小的,它指明addr结构所占有的字节个数。同样的,它也可以被设置为NULL。
返回值:非负描述字——成功, -1——失败
accept默认会阻塞进程,直到有一个客户连接建立后返回,它返回的是一个新可用的套接字,这个套接字是连接套接字。此时我们需要区分两种套接字,一种套接字正如accept的参数sockfd,它是监听套接字,在调用listen函数之后,一个套接字会从主动连接的套接字变身为一个监听套接字;而accept返回是一个连接套接字,它代表着一个网络已经存在的点点连接。自然要问的是:为什么要有两种套接字?原因很简单,如果使用一个描述字的话,那么它的功能太多,使得使用很不直观,同时在内核确实产生了一个这样的新的描述字。
如果accept成功返回,则服务器与客户已经正确建立连接了,此时服务器通过accept返回的套接字来完成与客户的通信。
【send】
send函数
int send( SOCKET s,    const char FAR *buf,    int len,    int flags ); 
不论是客户还是服务器应用程序都用send函数来向TCP连接的另一端发送数据。
客户程序一般用send函数向服务器发送请求,而服务器则通常用send函数来向客户程序发送应答。
第一个参数指定发送端套接字描述符;
第二个参数指明一个存放应用程序要发送数据的缓冲区;
第三个参数指明实际要发送的数据的字节数;
第四个参数一般置0。
【recv】
recv函数
int recv( SOCKET s,   char FAR *buf,    int len,   int flags   );  
不论是客户还是服务器应用程序都用recv函数从TCP连接的另一端接收数据。
第一个参数指定接收端套接字描述符;
第二个参数指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;
第三个参数指明buf的长度;
第四个参数一般置0。

【getaddrinfo】

能够处理名字到地址以及服务器到端口这两种转换,返回的是一个addrinfo的结构指针。

包含的头文件#include<netdb.h>

函数原型

int getaddrinfo( const char *hostname, const char *service, const struct addrinfo *hints, struct addrinfo **result );

hostname:一个主机名或者地址串

service:服务器名可以是十进制的端口号

hints:可以是一个空指针,也可以是一个指向某个addrinfo结构体的指针

result:本函数通过result指针参数返回一个指向addrinfo结构体链表的指针

返回值:0--成功;非0--出错

struct addrinfo {    

int ai_flags;        //AI_PASSIVE,AI_CANONNAME,AI_NUMERICHOST    

int ai_family;        //AF_INET,AF_INET6    

int ai_socktype;    //SOCK_STREAM,SOCK_DGRAM   

int ai_protocol;    //IPPROTO_IP, IPPROTO_IPV4, IPPROTO_IPV6 etc.    

size_t ai_addrlen;            //must be zero or a null pointer    

char* ai_canonname;            //must be zero or a null pointer    

struct sockaddr* ai_addr;    //must be zero or a null pointer    

struct addrinfo* ai_next;    //must be zero or a null pointer

};

【memset和bzero的区别】

void *memset(void *s, int ch, size_t n);  函数解释:将s中前n个字节替换为ch并返回s; 

extern void bzero(void *s, int n);  用法:#include <string.h>  功能:置字节字符串s的前n个字节为零且包括‘\0’。  说明:bzero无返回值

【程序见附件】

 

server端:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

char buf[0x1000] = {0};
char buff[0x1000] = {0};

int main()
{
    int sockfd,acceptfd;
//socket初始化
    struct sockaddr_in addr;//sockaddr_in 结构体包含在netinet/in.h头文件里

    if((sockfd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP)) < 0)
    {
        printf("socket failed!\n");
    }

    addr.sin_family = AF_INET;
    addr.sin_port = htons(12345);
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");

    if(bind(sockfd,(struct sockaddr *)&addr,sizeof(addr)) == -1)
    {
        printf("bind failed!\n");
    }
//    printf("debug\n");
    if(listen(sockfd,SOMAXCONN) == -1)
    {
        printf("listen failed!\n");
    }

    if((acceptfd = accept(sockfd,NULL,NULL)) == -1)
    {
        printf("accept failed!\n");
    }
    
    if(recv(acceptfd,buf,0x1000,0) > 0)
    {
        printf("%s\n",buf);
        bzero(buf,0x1000);
    }

    strncpy(buff,"<html>\r\n",0x1000);
    strncat(buff,"<head>\r\n",0x1000);
    strncat(buff,"</head>\r\n",0x1000);
    strncat(buff,"<body>\r\n",0x1000);
    strncat(buff,"hello\r\n",0x1000);
    strncat(buff,"</body></html>\r\n",0x1000);

    send(acceptfd,buff,strlen(buff),0);

    return 0;
}

client端:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netdb.h>

char buf[0x1000] = "connect succeed!\n";
//char buf[0x1000] = "HTTP/1.1\r\nHost: 127.0.0.1:12345\r\nUser-Agent: Mozilla/5.0 (X11; Linux i686; rv:10.0.5) Gecko/20120606 Firefox/10.0.5\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nAccept-Language: zh-cn,zh;q=0.5\r\nAccept-Encoding: gzip, deflate\r\nConnection: keep-alive\r\nCache-Control: max-age=0\r\n\r\n";


int main(int argc,char **argv)
{
    int sockfd;
    struct sockaddr_in addr;
    struct addrinfo hints;
    struct addrinfo *ptr;
    struct addrinfo *res;

    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
//    hints.ai_protocol = 0;
    hints.ai_flags = (AI_V4MAPPED | AI_ADDRCONFIG);//此处必须设置ai_flags(man中是这么定义的)
    hints.ai_protocol = IPPROTO_TCP;//TCP

//    hints.ai_socktype = SOCK_DGRAM;

    if(getaddrinfo(argv[1],argv[2],&hints,&res) != 0)
    {
        printf("getaddrinfo failed!\n");
    }

    for(ptr = res;ptr != NULL;ptr = ptr->ai_next)
    {
        if(ptr->ai_family == AF_INET)
        {
            break;
        }
    }
    
    if((sockfd = socket(ptr->ai_family,ptr->ai_socktype,ptr->ai_protocol)) < 0)    
    {
        printf("socket failed!\n");
    }

    if(connect(sockfd,ptr->ai_addr,ptr->ai_addrlen) == -1)
    {
        printf("connect failed!\n");
    }
    
    send(sockfd,buf,sizeof(buf),0);    

    if(recv(sockfd,buf,0x1000,0) > 0)
    {
        printf("%s\n",buf);
        bzero(buf,0x1000);
    }
    

    return 0;
}

 

【运行如下】

在当前终端运行./server.c

另开终端运行./client 127.0.0.1:12345 

posted @ 2013-04-22 21:25  善辰  阅读(628)  评论(0编辑  收藏  举报