Head First C学习之BLAB

套接字

C程序有那个数据流读写字节,如文件,标准输入输出等,如果想写一个与网络通信的程序,就需要一种新的数据流-套接字(socket)

#include 
...
int listener_d = socket(PF_INET,SOCK_STREAM,0);
//listener_d似乎套接字的描述符->互联网套接字
//0是协议号,默认填0就可以。
、、、
if(listener_d == -1)
    error("无法打开套接字");

BLAB:服务器连接网络四部曲

B代表绑定端口(Bind)

L代表监听(Listen)

A代表接受连接(Accept)

B代表开始通信(Begin)

使用套接字于客户端程序通信前,服务器需要经历四个阶段:绑定(Bind)监听(Listen)接受(Accept)、和开始(Begin)

1、绑定端口

为了防止不同对话发送混乱,没醒服务必须使用不同的端口(port)

#include 
//为了创建互联网地址,需要包含这些头文件
...
struct sockaddr_in name;
name.sin_family = PF_INET;
name.sin_port =(in_port_t)htons(30000);
name.sin_addr.s_addr = htonl(INADDR_ANY);
//这些代码创建一个表示"互联网30000端口"套接字名
int c = bind(listener_d,(struct sockaddr *)&name,sizeof(name));
if(c == -1)
    error("无法绑定端口");

2、监听

如果创建的服务器出了名,可能会有很多客户端同时连接,想让客户端排队等待连接。

if(listen(listener_d,10) == -1)
    error("无法监听");
//列队长度为10,最对可以有10个客户端挺尸尝试连接服务器,它们不会立即等到响应,但可以排队等待,而第11个客户端回被服务器告知服务器太忙。

3、接受连接,

绑定完端口,设置完监听,唯一可以做的就是等待,accept()系统调用会一直等待,知道有客户端连接服务器时,它才会返回第二个套接字描述符,然后就可以用它通信了。

struct sockaddr_storage client_addr;
unsigned int address_size = sizeof(client_addr);
int connect_d = accept(listener_d,(struct sockaddr *)&client_addr,&address_size);
if(cinnect_d == -1)
    error("无法打开副套接字");

服务器将用新的连接描述符connect_d··· ··· 开始通信

套接字不是传统意义上的数据流

套接字是双向的,即可以用作输入,也可以有那个做输出,也就是说,他要用其他函数和他通信
如果想想套接字输出数据,要用send(),而不是fprintf().

char *msg = "Internet Konck-Knock Protocol Server\r\nVersion 1.0\r\nKonck!Knock!\r\n> ";
if(send(connect_d,meg,strlen(msg),0) == -1)
error("send")

绑定端口有延时

某个端口绑定套接字,接下来的30秒内,操作系统不予许任何程序再绑定它,包括商议绑定这个端口的程序。

int  reuse = 1;
//需要一个整形量来保存选项。
//设置为1,表示重新使用端口
if(setsockopt(listener_d,SOL_SOCKET,SO_REUSEADDR,(char*)&reuse,sizeof(int)) == -1)
    error("无法设置套接字的\"重新使用端口\"选项");

以上代码,让套接字重新使用已绑定过的端口。这样在第二次绑定端口时就不会发生错误了。

advice_server.c

#include 
#include 
#include 
#include 
#include 
#include  //close()
#include 

void error(char *msg)
{
    fprintf(stderr, "%s: %s\n",msg,strerror(errno) );
    exit(1);
}

int main(int argc, char *argv[])
{
    char *advice[]={
        "Take smaller bites\r\n",
        "Go for the tight jeans. No they do NOT make you look fat.\r\n",
        "One word:inappropriate\r\n",
        "Just for today ,be honest.Tell your boss what you *really* think\r\n",
        "You might want to rethink that haircut\r\n"
    };
    int listener_d = socket(PF_INET,SOCK_STREAM,0);
    if(listener_d == -1)
        error("无法打开套接字");
    //listener_d似乎套接字的描述符->互联网套接字
    //0是协议号,默认填0就可以。
    struct sockaddr_in name;
    name.sin_family = PF_INET;
    name.sin_port =(in_port_t)htons(30000);
    name.sin_addr.s_addr = htonl(INADDR_ANY);
    //这些代码创建一个表示"互联网30000端口"套接字名
    int  reuse = 1;
    //需要一个整形量来保存选项。
    //设置为1,表示重新使用端口
    if(setsockopt(listener_d,SOL_SOCKET,SO_REUSEADDR,(char*)&reuse,sizeof(int)) == -1)
        error("无法设置套接字的\"重新使用端口\"选项");

    if(bind(listener_d,(struct sockaddr *)&name,sizeof(name)) == -1)
        error("无法绑定端口");

    if(listen(listener_d,10) == -1)
        error("无法监听");

    puts("Wanting for connection");
    while(1)
    {
        struct sockaddr_storage client_addr;
        unsigned int address_size = sizeof(client_addr);
        int connect_d = accept(listener_d,(struct sockaddr *)&client_addr,&address_size);
        if(connect_d == -1)
            error("无法打开副套接字");
        char *msg =advice[rand() % 5];
        //rand 是一个随机函数
        send(connect_d,msg,strlen(msg),0);
    }   
    return 0;
}

从客户端读取数据

套接字用send()写数据,用recv()数据:

#include 
... 
<读了几个字节> = recv(<描述符>,<缓冲区>,<要读几个字节>,0);

如果用户在客户端输入一行文本,然后按下回车,recv()函数就会把文本保存在一个这样的字符数组中:

recv.jpg

  • 字符串不能以\0结尾。

  • 当用户在telent输入文本时,字符串以\r\n结尾。

  • recv()返回字符个数,如果发生错误就返回-1,如果客户端关闭了就返回0.

  • recv()调用不一定能一次接收到所有字符。

    最后一点很重要,他意味着可能需要多次调用recv()。

简单的recv()封装函数:

int read_in(int socket, char *buf,int len)
{
    char *s = buf;
    int slen = len;
    int c = recv(socket,s,slen,0);
    while((c >0)&&(s[c-1] != '\n')){
    s += c;
    slen -= c;
    c = recv(socket,s,slen,0);
    }
    if(c <0)
    return c;
    eles if(c == 0)
        buf[0] = '\0';
    else
        s[c-1] = '\0';
    return len - slen;
}

小测试:

Head First C学习之BLAB_第1张图片
Server_Client.jpg

代码:

#include 
#include 
#include 
#include 
#include 
#include  //close()
#include 
#include 

void error(char *msg)
{
    fprintf(stderr, "%s: %s\n",msg,strerror(errno) );
    exit(1);
}

/*
注册处理器函数
参数: 信号编号,处理器函数指针
*/
int  catch_signal(int sig,void (*handler)(int))
{
    struct sigaction action;
    action.sa_handler = handler;
    sigemptyset(&action.sa_mask);
    action.sa_flags = 0;
    return sigaction(sig,&action,NULL);
}

int open_listener_socket()
{
    int s = socket(PF_INET,SOCK_STREAM,0);
    if(s == -1)
        error("无法打开套接字");
    return s;
}

void bind_to_port(int socket,int port)
{
    struct sockaddr_in name;
    name.sin_family = PF_INET;
    name.sin_port =(in_port_t)htons(30000);
    name.sin_addr.s_addr = htonl(INADDR_ANY);
    //这些代码创建一个表示"互联网30000端口"套接字名
    int  reuse = 1;
    //需要一个整形量来保存选项。
    //设置为1,表示重新使用端口
    if(setsockopt(socket,SOL_SOCKET,SO_REUSEADDR,(char*)&reuse,sizeof(int)) == -1)
        error("无法设置套接字的\"重新使用端口\"选项");
    if(bind(socket,(struct sockaddr *)&name,sizeof(name)) == -1)
        error("无法绑定端口");

}

int read_in(int socket, char *buf,int len)
{
    char *s = buf;
    int slen = len;
    int c = recv(socket,s,slen,0);
    while((c >0)&&(s[c-1] != '\n')){
    s += c;
    slen -= c;
    c = recv(socket,s,slen,0);
    }
    if(c <0)
    return c;
    else if(c == 0)
        buf[0] = '\0';
    else
        s[c-1] = '\0';
    return len - slen;
}
/*
向客户端发送字符串
*/
int say(int socket,char *s)
{
    int result = send(socket,s,strlen(s),0);
    if(result == -1)
        fprintf(stderr, "%s:%s\n", "和客户端通信时发生错误",strerror(errno));
    return result;
}

int listener_d;//它将保存服务器的主监听套接字
/*
如果有人在服务器运行期间按住了Ctrl+C,这个函数就会在程序结束前关闭套接字
*/
void handle_shutdown(int sig)
{
    if(listener_d)
        close(listener_d);
    fprintf(stderr,"Bye!\n");
    exit (0);
}

int main(int argc, char const *argv[])
{
    if(catch_signal(SIGINT,handle_shutdown) == -1)
        error("Can't set the interrupt handler");
    listener_d = open_listener_socket();
    bind_to_port(listener_d,30000);
    if(listen(listener_d,10) == -1)
        error("can't listen");
    struct sockaddr_storage client_addr;
    unsigned int address_size = sizeof(client_addr);
    puts("Waiting for connection");
    char buf[255];
    while(1)
    {
        int connect_d = accept(listener_d,(struct sockaddr *)&client_addr,&address_size);
        if(connect_d == -1)
            error("Can't open secondary socket");
        if(say(connect_d,"Jnternet Knock-Knock Protocol server \r\nVersion 1.0\r\nKnkck! Knock!\r\n") != -1)
            read_in(connect_d,buf,sizeof(buf));
        if(strncasecmp("Who's there?",buf,12))
            say(connect_d,"You should say 'Who's there?'!");
        else{
            if(say(connect_d,"Oscar\r\n") != -1){
                read_in(connect_d,buf,sizeof(buf));
                if(strncasecmp("Oscar who?",buf,10))
                    say(connect_d,"You should say 'Oscar Who?'!\r\n");
                else
                    say(connect_d,"Oscar silly question,you get a silly answer\r\n");
            }
        }
        close(connect_d);
    } 
    
    return 0;
}

运行:

#./ikkp_server
#telnet  127.0.0.1 30000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Jnternet Knock-Knock Protocol server 
Version 1.0
Knkck! Knock!
#Who's there?
Oscar
#Oscar who?
Oscar silly question,you get a silly answer
Connection closed by foreign host.
#telnet  127.0.0.1 30000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Jnternet Knock-Knock Protocol server 
Version 1.0
Knkck! Knock!
#Come in
You should say 'Who's there?'!Connection closed by foreign host.

为每一个客户端fork()一个子进程

修改代码:添加fork()

while(1)
{
        int connect_d = accept(listener_d,(struct sockaddr *)&client_addr,&address_size);
        if(connect_d == -1)
            error("Can't open secondary socket");
        if(!fork()) //创建子进程,如果fork返回0,就说明你在子进程中
        {
            close(listener_d);//在子进程需要关闭住监听套接字,子进程只用connect_d和客户端通信。
            if(say(connect_d,"Jnternet Knock-Knock Protocol server \r\nVersion 1.0\r\nKnkck! Knock!\r\n") != -1)
                read_in(connect_d,buf,sizeof(buf));
            if(strncasecmp("Who's there?",buf,12))
                say(connect_d,"You should say 'Who's there?'!");
            else{
                if(say(connect_d,"Oscar\r\n") != -1){
                    read_in(connect_d,buf,sizeof(buf));
                    if(strncasecmp("Oscar who?",buf,10))
                        say(connect_d,"You should say 'Oscar Who?'!\r\n");
                    else
                        say(connect_d,"Oscar silly question,you get a silly answer\r\n");
                }
            }
            close(connect_d);
            exit(0);//退出子进程,防止子进程进入服务器的主循环。
        }
        close(connect_d);

    } 

你可能感兴趣的:(Head First C学习之BLAB)