分类 未分类 下的文章

linux编程学习 05-02 网络编程

上一篇:http://jinblog.com/archives/866.html

网络高级编程

  • 前面介绍的函数如recv,send,read和write等函数都是阻塞性函数,若资源没有准备好,则调用该函数的进程将进入阻塞状态,下面介绍两种I/O复用的解决方案

    • fcntl函数实现(非阻塞)

    • select函数

I/O多路转换-select函数

int select(int maxfdp1,fd_set readfds,fd_set writefds,fd_set exceptfds,struct timeval timeout);

  • 头文件

#include <sys/types.h>
#include <sys/time.h>
#include <unistd.h>
  • 返回准备就绪的描述符数,若超时则为0,若出错则为-1

  • timval结构体

struct timeval
{
        long tv_sec;//seconds
        long tv_usec;//and microseconds
};
  • 参数

    • maxfdp1:最大fd加1(max fd plus 1),在三个描述符集中找出最高描述符编号值,然后加1,就是第一个参数的值

    • readfds,writefds,exceptfds:是指向描述符集的指针。这三个描述符集说明了我们关小的可读,可写或处于异常条件的各个描述符。每个描述符集存放在一个fd_set数据类型中

    • timeout:指定愿意等待的时间

      • NULL:永远等待,知道捕捉到信号或文件描述符已准备好为止

      • 具体值:struct timeval类型的指针,若等待为timeout时间,还没有文件描述符准备好,就立即返回

      • 0:从不等待,测试所有指定的描述符立即返回

  • 传向select的参数告诉内核

    • 我们所关心的描述符

    • 对于每个描述符我们所关心的条件(是否可读一个给定的描述符,是否可写一个指定的描述符,是否关心一个描述符的异常条件)

    • 希望等待多长时间(可以永远等待,等待一个固定量的时间,或完全不等待)

  • 从select返回时内核告诉我们

    • 已准备好的描述符的数量

    • 哪一个描述符已准备好读,写或异常条件

    • 使用这种返回值,就可调用相应的I/O函数(一般是write或read),并且确知该函数不会阻塞

  • select函数根据希望进行的文件操作对文件描述符进行分类处理,这里,对文件描述符的处理主要设计4个宏函数

    • FD_ZERO(fd_set* set) 清除一个文件描述符集

    • FD_SET(int fd,fd_set* set) 将一个文件描述符加入文件描述符集中

    • FD_CLR(int fd,fd_set* set) 将一个文件描述符从文件描述符集中清除

    • FD_ISSET(int fd,fd_set* set) 测试该集中的一个给定位是否有变化

  • 在使用select函数之前,首先使用FD_ZERO和FD_SET来初始化文件描述符集,并使用select函数时,可循环使用FD_ISSET测试描述符集,在执行完成对相关的文件描述符后,可用FD_CLR来清除描述符集

fcntl函数实现示例

#include <stdio.h>
// atoi函数
#include <stdlib.h>
// wait函数
#include <sys/wait.h>
// I/O函数
#include <unistd.h>
#include <time.h>
// socket 系列函数
#include <sys/socket.h>
// sockaddr_in 结构体
#include <netinet/in.h>
// hton 函数
#include <arpa/inet.h>
#include <signal.h>
// memset函数
#include <string.h>
// pthread库
#include <pthread.h>
// errno
#include <errno.h>
// fcntl函数
#include <fcntl.h>
// ctrl + c时关掉服务器socket
void siginit_handle(int);
// 初始化指定数目的线程处理连接
void child_thread_init(int);
// 客户端链表节点结构体
struct client_fd
{
        int fd;
        struct client_fd* pre;
        struct client_fd* next;
};
typedef struct client_fd client_fd;
// 链表
client_fd* client_fd_link = NULL;
// 链表互斥锁
pthread_mutex_t link_mutex;
// 服务器socket
int server_fd = 0;
// 添加链表元素
int add_client(int);
// 删除链表元素,通过fd删除元素
int del_client_by_fd(int);
// 通过节点地址删除元素
int del_client_by_client(client_fd*);
// 服务器连接日志
void server_log(struct sockaddr_in*);
// ctrl + c关闭服务器socket
void sigint_handle(int sig);

int main(int argc,char* argv[])
{
        if(argc < 2)
        {
                printf("incorrect parameter\n");
                return 1;
        }
        if(SIG_ERR == signal(SIGINT,sigint_handle))
        {
                perror("signal error");
                return 1;
        }
        // 初始化互斥锁
        pthread_mutex_init(&link_mutex,NULL);
        struct sockaddr_in server_attr;
        memset(&server_attr,0,sizeof(server_attr));
        /*
         * 1. 创建socket
         * socket创建在内核中,是一个结构体
         * AF_INET : IPV4
         * SOCK_STREAM : TCP协议
         */
        server_fd = socket(AF_INET,SOCK_STREAM,0);
        if(server_fd == -1)
        {
                perror("sock error");
                return 1;
        }

        /*
         * 2. 设置SO_REUSEADDR,避免重启导致bind报错
         */
        int opt = 1;
        setsockopt(server_fd,SOL_SOCKET,SO_REUSEADDR,(void*)&opt,sizeof(int));
        /*
         * 3. 调用bind函数将socket与地址(ip,port)进行绑定
         */
        server_attr.sin_family = AF_INET;
        // 转换位网络字节序
        server_attr.sin_port = htons((short)atoi(argv[1]));
        // 表示所有ip,也可以指定第一个ip,指定ip时要把主机字节序转换成网络字节序
        server_attr.sin_addr.s_addr = INADDR_ANY;
        // 绑定ip端口
        if(-1 == bind(server_fd,(struct sockaddr*)&server_attr,sizeof(server_attr)))
        {
                perror("bind error");
                return 1;
        }
        /*
         * 4. 调用listen函数启动监听,通知系统接收来自客户端的请求
         * 第二个参数代表客户端队列的长度
         * 执行成功后能用netstat查看
         */
        if(-1 == listen(server_fd,8))
        {
                perror("listen error");
                return 1;
        }

        // 忽略SIGPIPE信号处理函数,避免由于客户端断开连接,并由于read发送SIGPIPE信号导致进程结>束
        signal(SIGPIPE,SIG_IGN);
        int client_fd = 0;
        struct sockaddr_in client_attr;
        socklen_t addrlen = sizeof(client_attr);
        int client_flag = 0;
        // 开启子线程处理连接·
        child_thread_init(8);
        while(1)
        {
                /*
                * 5. 调用accept获得客户端连接,并返回一个新的socket文件描述符,新的文件描述符放>进client_fd链表中
                * 没有客户端连接,此函数会阻塞,直到获得一个客户端连接
                */
                client_fd = accept(server_fd,(struct sockaddr*)&client_attr,&addrlen);
                if(client_fd <= 0)
                {
                        continue;
                }
                server_log(&client_attr);
                // 修改socket为非阻塞读写
                client_flag = fcntl(client_fd,F_GETFL);
                client_flag |= O_NONBLOCK;
                fcntl(client_fd,F_SETFL,client_flag);
                add_client(client_fd);
                // 线程是共享的进程资源,这里不要关闭,不然线程里面的也没了
                // close(client_fd);
        }
        // close(server_fd);
        return 0;
}
void sigint_handle(int sig)
{
        close(server_fd);
        exit(0);
}
void server_log(struct sockaddr_in* client_attr)
{
        char ip[16] = {'\0'};
        // 结构体的的数据不能直接显示,需要先转换成本机字节序
        inet_ntop(AF_INET,&client_attr->sin_addr.s_addr,ip,sizeof(ip));
        printf("connected by %s:%d\n",ip,ntohs(client_attr->sin_port));
}
void* thread_handle(void* data)
{
        /*
        * 5. 遍历客户端socket_fd,对有输入的客户端进行回应
        */
        client_fd* temp;
        char res[512] = {'\0'},buf[512] = {'\0'};
        int read_len = 0,res_len = sizeof(res);
        while(1)
        {
                temp = client_fd_link;
                while(temp != NULL)
                {
                        read_len = read(temp->fd,res,res_len);
                        // 客户端关闭,则关闭客户端连接
                        if(read_len == 0)
                        {
                                perror("read");
                                del_client_by_client(temp);
                                break;
                        }
                        else if(read_len > 0)
                        {
                                // buf 与 res不能一样
                                sprintf(buf,"thread : %lu\nmessage : %s\n",pthread_self(),res);

                                sprintf(res,"%s\n",buf);

                                if(write(temp->fd,res,res_len) != res_len)
                                {
                                        // 客户端关闭,则关闭客户端连接
                                        if(errno == EPIPE)
                                        {
                                                perror("write");
                                                del_client_by_client(temp);
                                                break;
                                        }
                                }
                        }
                        temp = temp->next;
                }
        }
        return (void*)NULL;
}
void child_thread_init(int max)
{
        pthread_t tid = 0;
        // 初始化线程属性,用来创建分离的线程,使子线程结束后自动回收
        pthread_attr_t thread_attr;
        pthread_attr_init(&thread_attr);
        pthread_attr_setdetachstate(&thread_attr,PTHREAD_CREATE_DETACHED);
        // 子线程处理I/O,达到并发的效果
        while(max > 0)
        {
                pthread_create(&tid,&thread_attr,thread_handle,(void*)NULL);
                max--;
        }
}
// 添加链表元素
int add_client(int fd)
{
        pthread_mutex_lock(&link_mutex);
        if(client_fd_link == NULL)
        {
                client_fd_link = malloc(sizeof(client_fd));
                client_fd_link->pre = NULL;
                client_fd_link->next = NULL;
                client_fd_link->fd = fd;
                pthread_mutex_unlock(&link_mutex);
                return 0;
        }
        client_fd* temp = client_fd_link;
        while(temp->next != NULL)
        {
                temp = temp->next;
        }
        temp->next = malloc(sizeof(client_fd));
        temp->next->pre = temp;
        temp->next->next = NULL;
        temp->next->fd = fd;
        pthread_mutex_unlock(&link_mutex);
        return 0;
}
// 删除链表元素,通过fd删除元素
int del_client_by_fd(int fd)
{
        pthread_mutex_lock(&link_mutex);
        if(client_fd_link == NULL)
        {
                pthread_mutex_unlock(&link_mutex);
                return -1;
        }
        client_fd* temp = client_fd_link->next;
        // 链表第一个元素比较特殊,做特殊处理
        if(fd == client_fd_link->fd)
        {
                free(client_fd_link);
                client_fd_link = temp;
                if(client_fd_link != NULL)
                {
                        client_fd_link->pre = NULL;
                }
                pthread_mutex_unlock(&link_mutex);
                return 0;
        }
        while(temp != NULL)
        {
                if(temp->fd == fd)
                {
                        temp->pre->next = temp->next;
                        if(temp->next != NULL)
                        {
                                temp->next->pre = temp->pre;
                        }
                        close(temp->fd);
                        free(temp);
                        pthread_mutex_unlock(&link_mutex);
                        return 0;
                }
                temp = temp->next;
        }
        pthread_mutex_unlock(&link_mutex);
        return -1;
}
// 通过节点地址删除元素
int del_client_by_client(client_fd* client)
{
        pthread_mutex_lock(&link_mutex);
        if(client_fd_link == NULL)
        {
                pthread_mutex_unlock(&link_mutex);
                return -1;
        }
        client_fd* temp = client_fd_link->next;
        // 链表第一个元素比较特殊,做特殊处理
        if(client == client_fd_link)
        {
                free(client_fd_link);
                client_fd_link = temp;
                if(client_fd_link != NULL)
                {
                        client_fd_link->pre = NULL;
                }
                pthread_mutex_unlock(&link_mutex);
                return 0;
        }
        while(temp != NULL)
        {
                if(temp == client)
                {
                        temp->pre->next = temp->next;
                        if(temp->next != NULL)
                        {
                                temp->next->pre = temp->pre;
                        }
                        close(temp->fd);
                        free(temp);
                        pthread_mutex_unlock(&link_mutex);
                        return 0;
                }
                temp = temp->next;
        }
        pthread_mutex_unlock(&link_mutex);
        return -1;
}

select函数实现示例

#include <stdio.h>
// atoi函数
#include <stdlib.h>
// wait函数
#include <sys/wait.h>
// I/O函数
#include <unistd.h>
#include <time.h>
// socket 系列函数
#include <sys/socket.h>
// sockaddr_in 结构体
#include <netinet/in.h>
// hton 函数
#include <arpa/inet.h>
#include <signal.h>
// memset函数
#include <string.h>
// pthread库
#include <pthread.h>
// errno
#include <errno.h>
// fcntl函数
#include <fcntl.h>
#include <sys/types.h>
#include <sys/time.h>
// ctrl + c时关掉服务器socket
void siginit_handle(int);
// 初始化指定数目的线程处理连接
void child_thread_init(int);
// 客户端链表节点结构体
struct client_fd
{
        int fd;
        struct client_fd* pre;
        struct client_fd* next;
};
typedef struct client_fd client_fd;
// 链表
client_fd* client_fd_link = NULL;
// 链表互斥锁
pthread_mutex_t link_mutex;
// 服务器socket
int server_fd = 0;
// 添加链表元素
int add_client(int);
// 删除链表元素,通过fd删除元素
int del_client_by_fd(int);
// 通过节点地址删除元素
int del_client_by_client(client_fd*);
// 服务器连接日志
void server_log(struct sockaddr_in*);
// ctrl + c关闭服务器socket
void sigint_handle(int sig);
// 把链表中的fd加入到set中,并返回socket描述符最大的一个
int init_set(fd_set*);
int main(int argc,char* argv[])
{
        if(argc < 2)
        {
                printf("incorrect parameter\n");
                return 1;
        }
        if(SIG_ERR == signal(SIGINT,sigint_handle))
        {
                perror("signal error");
                return 1;
        }
        // 初始化互斥锁
        pthread_mutex_init(&link_mutex,NULL);
        struct sockaddr_in server_attr;
        memset(&server_attr,0,sizeof(server_attr));
        /*
         * 1. 创建socket
         * socket创建在内核中,是一个结构体
         * AF_INET : IPV4
         * SOCK_STREAM : TCP协议
         */
        server_fd = socket(AF_INET,SOCK_STREAM,0);
        if(server_fd == -1)
        {
                perror("sock error");
                return 1;
        }

        /*
         * 2. 设置SO_REUSEADDR,避免重启导致bind报错
         */
        int opt = 1;
        setsockopt(server_fd,SOL_SOCKET,SO_REUSEADDR,(void*)&opt,sizeof(int));
        /*
         * 3. 调用bind函数将socket与地址(ip,port)进行绑定
         */
        server_attr.sin_family = AF_INET;
        // 转换位网络字节序
        server_attr.sin_port = htons((short)atoi(argv[1]));
        // 表示所有ip,也可以指定第一个ip,指定ip时要把主机字节序转换成网络字节序
        server_attr.sin_addr.s_addr = INADDR_ANY;
        // 绑定ip端口
        if(-1 == bind(server_fd,(struct sockaddr*)&server_attr,sizeof(server_attr)))
        {
                perror("bind error");
                return 1;
        }
        /*
         * 4. 调用listen函数启动监听,通知系统接收来自客户端的请求
         * 第二个参数代表客户端队列的长度
         * 执行成功后能用netstat查看
         */
        if(-1 == listen(server_fd,8))
        {
                perror("listen error");
                return 1;
        }

        // 忽略SIGPIPE信号处理函数,避免由于客户端断开连接,并由于read发送SIGPIPE信号导致进程结>束
        signal(SIGPIPE,SIG_IGN);
        int client_fd = 0;
        struct sockaddr_in client_attr;
        socklen_t addrlen = sizeof(client_attr);
        // 开启子线程处理连接·
        child_thread_init(8);
        while(1)
        {
                /*
                * 5. 调用accept获得客户端连接,并返回一个新的socket文件描述符,新的文件描述符放>进client_fd链表中
                * 没有客户端连接,此函数会阻塞,直到获得一个客户端连接
                */
                client_fd = accept(server_fd,(struct sockaddr*)&client_attr,&addrlen);
                if(client_fd <= 0)
                {
                        continue;
                }
                server_log(&client_attr);
                add_client(client_fd);
                // 线程是共享的进程资源,这里不要关闭,不然线程里面的也没了
        }
        return 0;
}
void sigint_handle(int sig)
{
        close(server_fd);
        exit(0);
}
void server_log(struct sockaddr_in* client_attr)
{
        char ip[16] = {'\0'};
        // 结构体的的数据不能直接显示,需要先转换成本机字节序
        inet_ntop(AF_INET,&client_attr->sin_addr.s_addr,ip,sizeof(ip));
        printf("connected by %s:%d\n",ip,ntohs(client_attr->sin_port));
}
void response(client_fd* temp)
{
        char res[512] = {'\0'},buf[512] = {'\0'};
        int read_len = 0,res_len = sizeof(res);
        read_len = read(temp->fd,res,res_len);
        // 客户端关闭,则关闭客户端连接
        if(read_len == 0)
        {
                printf("close connection\n");
                del_client_by_client(temp);
                return;
        }
        else if(read_len > 0)
        {
                // buf 与 res不能一样
                sprintf(buf,"thread : %lu\nmessage : %s\n",pthread_self(),res);

                sprintf(res,"%s\n",buf);

                if(write(temp->fd,res,res_len) != res_len)
                {
                        // 客户端关闭,则关闭客户端连接
                        if(errno == EPIPE)
                        {
                                printf("close connection\n");
                                del_client_by_client(temp);
                                return;
                        }
                }
        }
}
void* thread_handle(void* data)
{
        /*
        * 5. 遍历客户端socket_fd,对有输入的客户端进行回应
        */
        client_fd* temp;
        fd_set set;
        int max = init_set(&set);
        struct timeval time = {2,0};
        int n = 0;
        while(1)
        {
                // printf("n : %d;max : %d\n",n,max);
                // 注意:是文件描述符最大的加1,
                n = select(max + 1,&set,NULL,NULL,&time);
                temp = client_fd_link;
                while(temp != NULL && n > 0)
                {
                        if(FD_ISSET(temp->fd,&set) == 0)
                        {
                                continue;
                        }
                        response(temp);
                        temp = temp->next;
                        n--;
                }
                // 时间需要重新设置
                time.tv_sec = 2;
                time.tv_usec = 0;
                // 用最新的连接信息设置set,重设最大值
                max = init_set(&set);
        }
        return (void*)NULL;
}
void child_thread_init(int max)
{
        pthread_t tid = 0;
        // 初始化线程属性,用来创建分离的线程,使子线程结束后自动回收
        pthread_attr_t thread_attr;
        pthread_attr_init(&thread_attr);
        pthread_attr_setdetachstate(&thread_attr,PTHREAD_CREATE_DETACHED);
        // 子线程处理I/O,达到并发的效果
        while(max > 0)
        {
                pthread_create(&tid,&thread_attr,thread_handle,(void*)NULL);
                max--;
        }
}
// 添加链表元素
int add_client(int fd)
{
        pthread_mutex_lock(&link_mutex);
        if(client_fd_link == NULL)
        {
                client_fd_link = malloc(sizeof(client_fd));
                client_fd_link->pre = NULL;
                client_fd_link->next = NULL;
                client_fd_link->fd = fd;
                pthread_mutex_unlock(&link_mutex);
                return 0;
        }
        client_fd* temp = client_fd_link;
        while(temp->next != NULL)
        {
                temp = temp->next;
        }
        temp->next = malloc(sizeof(client_fd));
        temp->next->pre = temp;
        temp->next->next = NULL;
        temp->next->fd = fd;
        pthread_mutex_unlock(&link_mutex);
        return 0;
}
// 删除链表元素,通过fd删除元素
int del_client_by_fd(int fd)
{
        pthread_mutex_lock(&link_mutex);
        if(client_fd_link == NULL)
        {
                pthread_mutex_unlock(&link_mutex);
                return -1;
        }
        client_fd* temp = client_fd_link->next;
        // 链表第一个元素比较特殊,做特殊处理
        if(fd == client_fd_link->fd)
        {
                free(client_fd_link);
                client_fd_link = temp;
                if(client_fd_link != NULL)
                {
                        client_fd_link->pre = NULL;
                }
                pthread_mutex_unlock(&link_mutex);
                return 0;
        }
        while(temp != NULL)
        {
                if(temp->fd == fd)
                {
                        temp->pre->next = temp->next;
                        if(temp->next != NULL)
                        {
                                temp->next->pre = temp->pre;
                        }
                        close(temp->fd);
                        free(temp);
                        pthread_mutex_unlock(&link_mutex);
                        return 0;
                }
                temp = temp->next;
        }
        pthread_mutex_unlock(&link_mutex);
        return -1;
}
// 通过节点地址删除元素
int del_client_by_client(client_fd* client)
{
        pthread_mutex_lock(&link_mutex);
        if(client_fd_link == NULL)
        {
                pthread_mutex_unlock(&link_mutex);
                return -1;
        }
        client_fd* temp = client_fd_link->next;
        // 链表第一个元素比较特殊,做特殊处理
        if(client == client_fd_link)
        {
                free(client_fd_link);
                client_fd_link = temp;
                if(client_fd_link != NULL)
                {
                        client_fd_link->pre = NULL;
                }
                pthread_mutex_unlock(&link_mutex);
                return 0;
        }
        while(temp != NULL)
        {
                if(temp == client)
                {
                        temp->pre->next = temp->next;
                        if(temp->next != NULL)
                        {
                                temp->next->pre = temp->pre;
                        }
                        close(temp->fd);
                        free(temp);
                        pthread_mutex_unlock(&link_mutex);
                        return 0;
                }
                temp = temp->next;
        }
        pthread_mutex_unlock(&link_mutex);
        return -1;
}
int init_set(fd_set* set)
{
        client_fd* temp = client_fd_link;
        // 清空set
        FD_ZERO(set);
        int max = 0;
        while(temp != NULL)
        {
                // 获取最大的set
                if(max < temp->fd)
                {
                        max = temp->fd;
                }
                // 把client_fd加入set中
                FD_SET(temp->fd,set);
                temp = temp->next;
        }
        return max;
}

守护进程

  • 守护进程(deamon)是生存期长的一种进程。他们常常在系统装入时启动,在系统关闭时终止。

  • 所有守护进程都以超级用户(用户ID为0)的优先权运行

  • 守护进程没有控制终端

  • 守护进程的父进程都是init进程

守护进程编程步骤

  1. 使用umask将文件模式创建创建屏蔽字设置为0

  2. 调用fork,然后让父进程退出(exit)

  3. 调用setsid创建一个新会话

  4. 将当前工作目录更改为根目录

  5. 关闭不需要的文件描述符

守护进程出错处理

  • 由于守护进程完全脱离了控制终端,因此,不能像其他程序一样通过输出错误信息到控制台的方式来通知程序员

  • 通常的方式是使用syslog服务,将出错信息输入到“/var/log/syslog”系统日志文件中

  • syslog是linux中的系统日志管理服务,通过守护进程syslog来维护

syslog服务说明

  • openlog函数用于打开系统日志服务的一个连接

  • syslog函数用于向日志文件中写入消息,在这里可以规定消息的优先级,消息的输出格式等

  • closelog函数用于关闭系统日志服务的连接

openlog函数

void openlog(char* ident,int option,int facility);

  • 头文件

#include <syslog.h>
  • 参数

    • ident:要向每个消息加入的字符串,通常为程序名称

    • option

      • LOG_CONS 若日志消息不能通过发送至syslog,则将该消息写至控制台

      • LOG_NDELAY 立即打开linux域数据报套接口至syslog守护进程。通常,在记录第一条消息之前,该套接口不打开

      • LOG_PERROR 除将日志发送给syslog外,还将他写至stderr

      • LOG_PID 每条消息都包含进程id,此选择项可供对每个请求都fork一个子进程的守护进程使用

    • facility

      • LOG_AUTH 授权程序,如login,su,getty等

      • LOG_CRON cron和at

      • LOG_DAEMON 系统守护进程,如ftpd,routed等

      • LOG_KERN 内核产生的消息

      • LOG_LOCAL0~7 保留由本地使用

      • LOG_LPR 行打系统,如lpd,lpc等

      • LOG_MAIL 邮件系统

      • LOG_NEWSU senet网络新闻系统

      • LOG_SYSLOG syslog守护进程本身(用这个就ok)

      • LOG_USER 来自其他用户进程的消息

      • LOG_UUCP UUCP系统

syslog和closelog函数

void syslog(int priority,char* format,...);
void closelog(void);

  • 头文件

#include <syslog.h>
  • 参数

    • priority:消息优先级

      • LOG_EMERG 紧急(系统不可使用,最高优先级)

      • LOG_ALERT 必须立即修复的条件

      • LOG_CRIT 临界条件(例如,硬设备出错)

      • LOG_ERR 出错条件

      • LOG_WARNING 警告条件

      • LOG_NOTICE 正常,但重要的条件

      • LOG_INFO 信息性消息

      • LOG_DEBUG 调试排错消息(最低优先级)

示例

示例为select函数例子的main函数部分,注意要把头文件加上去和其他部分补齐才能运行

int main(int argc,char* argv[])
{
        if(argc < 2)
        {
                printf("incorrect parameter\n");
                return 1;
        }
        if(SIG_ERR == signal(SIGINT,sigint_handle))
        {
                perror("signal error");
                return 1;
        }
        /* 守护进程变成步骤  */
        // 1. 使用umask将文件模式创建屏蔽字设置为0
        umask(0);
        // 2. 调用fork,然后让父进程退出
        int pid = fork();
        if(pid > 0)
        {
                exit(0);
        }
        // 3. 调用setsid创建一个新会话
        setsid();
        // 4. 将当前工作目录更改为根目录
        chroot("/");
        // 5. 关闭不需要的文件描述符
        close(STDIN_FILENO);
        close(STDOUT_FILENO);
        close(STDERR_FILENO);

        /* 使用syslog */
        // 1. 打开
        openlog("jin",LOG_PID,LOG_SYSLOG);
        // 2. 记录
        syslog(LOG_INFO,"jin deamon start");
        // 3. 关闭
        closelog();

        // 初始化互斥锁
        pthread_mutex_init(&link_mutex,NULL);
        struct sockaddr_in server_attr;
        memset(&server_attr,0,sizeof(server_attr));
        /*
         * 1. 创建socket
         * socket创建在内核中,是一个结构体
         * AF_INET : IPV4
         * SOCK_STREAM : TCP协议
         */
        server_fd = socket(AF_INET,SOCK_STREAM,0);
        if(server_fd == -1)
        {
                perror("sock error");
                return 1;
        }

        /*
         * 2. 设置SO_REUSEADDR,避免重启导致bind报错
         */
        int opt = 1;
        setsockopt(server_fd,SOL_SOCKET,SO_REUSEADDR,(void*)&opt,sizeof(int));
        /*
         * 3. 调用bind函数将socket与地址(ip,port)进行绑定
         */
        server_attr.sin_family = AF_INET;
        // 转换位网络字节序
        server_attr.sin_port = htons((short)atoi(argv[1]));
        // 表示所有ip,也可以指定第一个ip,指定ip时要把主机字节序转换成网络字节序
        server_attr.sin_addr.s_addr = INADDR_ANY;
        // 绑定ip端口
        if(-1 == bind(server_fd,(struct sockaddr*)&server_attr,sizeof(server_attr)))
        {
                perror("bind error");
                return 1;
        }
        /*
         * 4. 调用listen函数启动监听,通知系统接收来自客户端的请求
         * 第二个参数代表客户端队列的长度
         * 执行成功后能用netstat查看
         */
        if(-1 == listen(server_fd,8))
        {
                perror("listen error");
                return 1;
        }

        // 忽略SIGPIPE信号处理函数,避免由于客户端断开连接,并由于read发送SIGPIPE信号导致进程结>束
        signal(SIGPIPE,SIG_IGN);
        int client_fd = 0;
        struct sockaddr_in client_attr;
        socklen_t addrlen = sizeof(client_attr);
        // 开启子线程处理连接·
        child_thread_init(8);
        while(1)
        {
                /*
                * 5. 调用accept获得客户端连接,并返回一个新的socket文件描述符,新的文件描述符放>进client_fd链表中
                * 没有客户端连接,此函数会阻塞,直到获得一个客户端连接
                */
                client_fd = accept(server_fd,(struct sockaddr*)&client_attr,&addrlen);
                if(client_fd <= 0)
                {
                        continue;
                }
                server_log(&client_attr);
                add_client(client_fd);
                // 线程是共享的进程资源,这里不要关闭,不然线程里面的也没了
        }
        return 0;
}

linux编程学习 05-01 网络编程

计算机联网的目的

  • 使用远程资源

  • 共享信息,程序和数据

  • 分布处理

协议

  • 计算机网络中实现通信必须有一些约定,如对速率,传输代码,代码结构,传输控制步骤和出错控制步骤等约定,这些约定即被称为通信协议

  • 在两个节点之间要成功进行通信,两个节点之间必须使用相同的“语言”,这些被通信各方共同遵守的约定,语言,规则被称为协议

  • 在internet中,最为通用的网络协议是TCP/IP协议

TCP/IP协议族

  • TCP/IP实际上是一个一起工作的通信家族,为网际数据通信通路

  • TCP/IP协议族大体上分为三个部分

    • internet协议,如IP协议,对应网络层

    • 传输控制协议(TCP)和用户数据报文协议(UDP),对应传输层

    • 处于TCP和UDP之上的一组协议专门开发的应用程序。他们包括:远程登陆协议(TELNET),文件传送协议(FTP),域名服务(DNS)和简单的邮件传送程序(SMTP),超文本传输协议(HTTP)等,对应应用层

网络层协议

  • internet协议(IP)

    • 该协议被设计成互联网分组交换通信网,以形成一个网际通信环境。他负责在源主机和目的主机之间传输来自其较高层软件的称为数据报文的数据块,他在源和目的地之间提供非连接型传递服务

  • 网际控制报文协议(ICMP)

    • ICMP实际上不是IP层部分,但直接同IP层一起工作,报告网络上的某些出错情况。允许网际路由器传输出错信息或测试报文。

  • 地址识别协议(ARP)
    -ARP实际上不是网络层部分,他处于IP和数据链路层之间,他是在32位IP地址和48位局域网物理地址之间执行翻译的协议。mac地址与IP地址的转换

传输层协议

  • 传输控制协议(TCP)

    • 可靠的面向连接的传输层服务

    • 主要功能

      • 监听输入对话建立请求

      • 请求另一网络站点对话

      • 可靠的发送和接收数据

      • 适度的关闭对话

  • 用户数据报文协议(UDP)

    • UDP提供不可靠的非连接型传输层服务

      • 他允许在源和目的站点之间传送数据,而不必在传送数据之前建立对话

      • 不使用TCP使用端对端差错校验

      • 传输层功能全部发挥,而开销却比较低

      • 主要用于那些不要求TCP协议的非连接型应用程序。例如,名字服务,网络管理,视频点播和网络会议等

应用层协议

  • TELNET:远程登陆

  • FTP和TFTP:文件传送协议

  • SMTP:简单的文件传送协议

  • DNS:域名服务

  • HTTP:超文本传输协议

Internet协议(IP)

  • IP的主要目的是为数据输入/输出网络提供基本算法,为高层协议提供无连接的传送服务。这意味着在IP将数据递交给接收站点以前不在传输站点和接收站点之间建立对话(虚拟链路)。它只是封装和传递数据,但不向发送者和接收者报告包的状态,不处理所原道的故障。

  • IP协议主要有以下四个主要功能

    • 数据传送

    • 寻址

    • 路由选择

    • 数据报文的分段

  • IP协议不注意包内的数据类型,他所知道的一切是必须将称为IP帧头的控制协议加到高层协议(TCP或者UDP)所接收的数据上

IP地址

  • 在TCP/IP网络中,每个主机都有唯一的地址,他是通过IP协议来实现的。

  • IP协议要求在每次与TCP/IP网络建立连接时,每台主机都必须为这个连接分配一个唯一的32位地址,因为在这个32位IP地址,不但可以用来识别某一台主机,而且还隐含着网际间的路径信息

  • 主机是指网络上的一个节点,不能简单的理解为一台计算机,实际上IP地址是分配给计算机的网络适配器(网卡)的,一台计算机可以有多个网络适配器,就可以有多个IP地址,一个网络适配器就是一个节点

  • IP地址为32位地址,一般以4个字节表示。每个字节的数字又用十进制表示,即每个字节的数的范围是0~255,且每个数字之间用“.”隔开,例如:192.168.1.8,这种记录方法称为“点-分”十进制号发。IP地址结构如下
    |网络类型|网络ID|主机ID|

IP地址分类

  • Internet地址可分为5类

地址类型第一个字节的十进制数
A000-127
B128-191
C192-233
D224-239
E240-255
  • A,B,C三类有InterNIC(Internet网络信息中心)在全球范围内同一分配,D,E类为特殊地址

端口号

  • TCP/UDP协议使用16位整数存储端口号,所以每个主机拥有65535个端口

  • 一些端口被IANA分配给指定应用

    • 21:FTP

    • 23:Telnet

    • 80:HTTP

    • RFC 1700(大约有2000个保留端口)

传输控制协议(TCP)

  • TCP(传输控制协议Transmission Control Protocol)是重要的传输层协议,TCP提供一种面向连接的,可靠的字节流服务

  • TCP协议的目的是允许数据同网络上的另外站点进行可靠的交换。他能提供端口编号的译码,以识别主机的应用程序,而且完成数据的可靠传输

  • TCP协议具有严格的内装差错校验算法确保数据的完整性

  • TCP协议是面向字节的顺序协议,这意味着包内的每个字节被分配一个顺序编号,并分配给每包一个顺序编号

用户数据报文协议(UDP)

  • UDP(用户数据报协议User Datagram Protocol)也是TCP/IP的传输层协议,他是无连接的,不可靠的传输服务。当接收数据时他不向发送方提供确认信息,他不提供输入包的顺序,如果出现丢失包或重份包的情况,也不会向发送方发出差错报文

    • 他允许在源和目的地站点间传送数据,而不必在传送数据之间建立对话

    • 不使用TCP使用的端对端差错校验

    • 传输层功能全部发挥,而开销却比较低

  • 由于他执行功能时具有较低的开销,因此执行速度比TCP快。他多半用于不需要可靠传输的应用程序,例如网络视频点播和视频会议等

TCP和UDP协议区别

  • TCP以连接为基础,即两台电脑必须先建立连接,然后才能传输数据,事实上,发送和接收的电脑必须一直互相通讯和联系。

  • UDP是一个无连接服务,数据可以直接发送而不必在两台电脑之间建立一个网络连接。他和有连接的TCP相比,占用带宽少,但是无法确认数据是否真正到达了客户端,而客户端收到的数据也不知道是否还是原来的发送数据

网络层其他数据路由协议

  • 路由协议分析数据包的地址并且决定传输数据到目的电脑最佳路线。他们也可以把大的数据分成几个部分,并且在目的地再把他们组合起来。IP处理实际上传输数据

    • ICMP(网络控制信息协议Internet Control Message Protocol)处理IP状态信息,比如能影响路由决策的数据错误或改变

    • RIP(路由信息协议Routing Information Protocol)他是几个决定信息传输的最佳路由路线协议中的一个

    • OSPF(Open Shortest Path First)一个用来决定路由的协议

    • ARP(地址解析协议Address Resolution Protocol)确定网络上一台电脑的数字地址

    • DNS(域名系统Domain Name System)从机器的名字确定机器的数字地址

    • RARP(反向地址解析协议Reverse Address Resolution Protocol)确定网络上一台计算机的地址,和ARP正好相反

其他用户服务协议

  • BOOTP(启动协议Boot Protocol)由网络服务器上取得启动信息,然后将本地的网络计算机启动

  • FTP(文件传输协议File Transfer Protocol)通过国际互联网从一台计算机上传输一个或多个文件到另一台计算机

  • TELNET(远程登陆)允许一个远程登陆,使用者可以从网络上的一台计算机通过TELNET连线到另一台机器,就向使用者直接在本地操作一样

  • EGP(外部网关协议Exterior Gateway Protocol)为外部网络传输路由信息

  • GGP(网关到网关协议Gateway-to-Gateway Protocol)在网关和网关之间传输路由协议

  • IGP(内部网关协议Interior Gateway Protocol)在内部网络传输路由协议

socket(套接字)

  • socket(套接字)是一种通讯机制,他包含一整套的调用接口和数据结构定义,他给应用程序提供了使用如TCP/UDP等网络协议进行通讯的手段

  • linux中的网络编程通过socket接口实现,socket既是一种特殊的I/O,提供对应的文件描述符。一个完整的socket都有一个相关描述(协议,本地地址,本地端口,远程地址,远程端口),每一个socket有一个本地的唯一socket,由操作系统分配

  • 内核提供的实现网络通信的接口

创建socket

int socket(int domain,int type,int protocol);

  • 返回:成功返回描述符,出错返回-1

  • 头文件

#include <sys/socket.h>
  • socket创建在内核中,若创建成功返回内核文件描述表的socket描述符

  • 参数

    • domain

      • AF_INET IPV4因特网域

      • AF_INET6 IPV6因特网域

      • AF_UNIX unix域

      • AF_UNSPEC 未指定

    • protocol

      • 通常为0,表示按给定的域和套接字类型选择默认协议

    • type

      • SOCK_STREAM

        • 流式的套接字可以提供可靠的,面向连接的通讯流。他使用了TCP协议。TCP保证了数据传输的正确性和顺序性

      • SOCK_DGRAM

        • 数据报套接字定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证可靠,无差错。使用数据报协议UDP协议

      • SOCK_RAW

        • 原始套接字允许对低层协议如IP或ICMP直接访问,主要用于新的网络协议实现的测试等

      • SOCK_SEQPACKET

        • 长度固定,有序,可靠的面向链接报文传递

字节序

  • 不同体系结构的主机使用不同字节序存储器中保存多字节整数。字节存储顺序不同,有的系统是高位在前,低位在后,有的系统是低位在前,高位在后

  • 字节序分为大端和小端字节序

  • 网络协议使用网络字节序即大端字节序

字节序转换函数

  • 网络传输的数据大家是一定要同一顺序的,所以对于内部字节表示顺序和网络字节顺序不同的机器,就一定要对数据进行转换

    • uint32_t htonl(uint32_t hostlong);

      • 将一个32位整数由主机字节序转换成网络字节序

    • uint12_t htons(uint16_t hostshort);

      • 将一个16位整数由主机字节序转换成网络字节序

    • uint32_t ntohl(uint32_t netlong);

      • 将一个32位整数由网络字节序转换成主机字节序

    • uint16_t ntohs(uint16_t netshort);

      • 将一个16位整数由网络字节序转换成主机字节序

通用地址结构

#include <sys/socket.h>
struct sockaddr
{
        unsigned short sa_family;// Internet 地址族 AF_XXX
        char sa_data[14];// 14bytes的协议地址
};
  • sa_data包含了一些远程电脑的地址,端口和套接字的数目,他里面的数据是杂溶在一起的

  • sa_family一般来说,IPV4使用AF_INET

  • 在传递给需要地址结构的函数时,把指向该结构体的指针转换成(struct sockaddr*)传递进去

因特网地址结构

#include <netinet/in.h>
struct in_addr
{
        in_addr_t s_addr;// ipv4地址
};
struct sockaddr_in
{
        short int sin_family;// internet地址族如AF_INET(主机字节序)
        unsigned short int sin_port;// 端口号,16位值(网络字节序)
        struct in_addr sin_addr;// Internet地址,32位ipv4地址(网络字节序)
        unsigned char sin_zero[8];// 添0(为了格式对齐的填充位)
};
  • 这两个(sockaddr和sockaddr_in)数据类型是等效的,可以相互转换,通常使用sockaddr_in更为方便

IPV4地址族和字符地址间的转换

const char* inet_ntop(int domain,const void*restrict addr,char*restrict str,socklen_t size);

  • 返回:成功返回地址字符串指针,出错返回NULL

  • 功能:网络字节序转换成点分十进制
    int inet_pton(int domain,const char*restrict str,void*restrict addr);

  • 返回:成功返回1,无效格式返回0,出错返回-1

  • 功能:点分十进制转换成网络字节序

  • 头文件

#include <arpa/inet.h>
  • 参数

    • domain:internet地址族地址,如AF_INET

    • addr:internet地址,32位IPV4地址(网络字节序)

    • str:地址字符串(点分十进制)指针

    • size:地址字符串大小

填写IPV4地址族结构案例

struct sockaddr_in sin;// 定义一个sockaddr_in结构体
char buf[16];
memset(&sin,0,sizeof(sin));// 内存清0
sin.sin_family = AF_INET;// 填写internet地址族
sin.sin_port = hton((short)3001);// 填写端口号,转换成网络字节序
// 填充sin_addr,需要吧字符串型的ip地址转换成网络字节序
if(inet_pton(AF_INET,"192.168.2.1",&sin.sin_addr.s_addr) <= 0)
{
        // 错误处理
}
// 网络字节序不能直接输出,需要从网络字节序转换回字符串型才能输出
printf("%s\n",inet_ntop(AF_INET,&sin.sin_addr.s_addr,buf,sizeof(buf)));

TCP客户端服务器编程模型

  • 客户端调用序列

    • 调用socket函数创建套接字

    • 调用connect连接服务器端

    • 调用I/O函数(read/write)与服务器端通讯

    • 调用close关闭套接字

  • 服务器端调用序列

    • 调用socket函数创建套接字

    • 调用bind绑定本地地址和端口

    • 调用listen启动监听

    • 调用accept从已连接队列中提取客户连接,没有客户连接会阻塞

    • 调用I/O函数(read/write)与客户端通讯

    • 调用close关闭套接字

套接字与地址绑定

绑定地址

int bind(int sockfd,const struct sockaddr* addr,socklen_t len);

  • 返回:成功返回0,出错返回-1

  • 头文件

#include <sys/socket.h>
查找绑定到套接字的地址

int getsockname(inf sockfd,struct sockaddr*restrict addr,socklen_t*restrict alenp);

  • 返回:成功返回0,出错返回-1

  • 头文件

#include <sys/socket.h>
获取对方地址

int getpeername(int sockfd,struct sockaddr*restrict addr,socklen_t*restrict alenp);

  • 返回:成功返回0,出错返回-1

  • 头文件

#include <sys/socket.h>

建立连接

服务器端

int listen(int sockfd,int backlog);

  • 返回:成功返回0,出错返回-1。backlog指定进行客户端连接排队的队列长度

  • 头文件

#include <sys/socket.h>

int accept(int sockfd,struct sockaddr*restrict addr,socklen_t*restrict len);

客户端

int connect(int sockfd,const struct sockaddr* addr,socklen_t len);

  • 返回:成功返回0,出错返回-1

  • 头文件

#include <sys/socket.h>

特殊bind地址

  • 一台主机可以有多个网络接口和多个IP地址,如果我们只关心某个地址的连接请求,我们可以指定一个具体的IP地址,如果要响应所有接口上的连接请求就要使用一个特殊的地址INADDR_ANY

  • #define INADDR_ANY (uint32_t)0x00000000

// 监听所有服务器上ip所得到的连接请求
struct sockaddr_in servaddr;
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_addr.s_addr = INADDR_ANY;

tcp示例

服务器端代码
#include <stdio.h>
// atoi函数
#include <stdlib.h>
// I/O函数
#include <unistd.h>
#include <time.h>
// socket 系列函数
#include <sys/socket.h>
// sockaddr_in 结构体
#include <netinet/in.h>
// hton 函数
#include <arpa/inet.h>
#include <signal.h>
// memset函数
#include <string.h>
// 服务器socket描述符
int server_fd = 0;
// 服务器连接属性
struct sockaddr_in server_attr;
// 客户端socket描述符
int client_fd = 0;
// 客户端连接属性
struct sockaddr_in client_attr;
// ctrl + c 关闭服务器监听
void sigint_handle(int);
// 响应客户端连接
void response(int);
// 服务器连接日志
void server_log(struct sockaddr_in*);
int main(int argc,char* argv[])
{
        if(argc < 2)
        {
                printf("incorrect parameter\n");
                return 1;
        }
        if(SIG_ERR == signal(SIGINT,sigint_handle))
        {
                perror("signal error");
                return 1;
        }
        memset(&server_attr,0,sizeof(server_attr));
        memset(&client_attr,0,sizeof(server_attr));
        /*
         * 1. 创建socket
         * socket创建在内核中,是一个结构体
         * AF_INET : IPV4
         * SOCK_STREAM : TCP协议
         */
        server_fd = socket(AF_INET,SOCK_STREAM,0);
        if(server_fd == -1)
        {
                perror("sock error");
                return 1;
        }
        /*
         * 2. 调用bind函数将socket与地址(ip,port)进行绑定
         */
        server_attr.sin_family = AF_INET;
        // 转换位网络字节序
        server_attr.sin_port = htons((short)atoi(argv[1]));
        // 表示所有ip,也可以指定第一个ip,指定ip时要把主机字节序转换成网络字节序
        server_attr.sin_addr.s_addr = INADDR_ANY;
        // 绑定ip端口
        if(-1 == bind(server_fd,(struct sockaddr*)&server_attr,sizeof(server_attr)))
        {
                perror("bind error");
                return 1;
        }
        /*
         * 3. 调用listen函数启动监听,通知系统接收来自客户端的请求
         * 第二个参数代表客户端队列的长度
         * 执行成功后能用netstat查看
         */
        if(-1 == listen(server_fd,8))
        {
                perror("listen error");
                return 1;
        }
        socklen_t addrlen = sizeof(client_attr);
        /*
         * 4. 调用accept获得客户端连接,并返回一个新的socket文件描述符
         * 若没有客户端连接,此函数会阻塞,直到获得一个客户端连接
         */
        while(1)
        {
                client_fd = accept(server_fd,(struct sockaddr*)&client_attr,&addrlen);
                server_log(&client_attr);
                /*
                 * 5. 调用I/O函数(read/write)和连接的客户端进行双向的通信
                 */
                response(client_fd);
                close(client_fd);
        }
        close(server_fd);
}
void sigint_handle(int sig)
{
        close(server_fd);
        exit(0);
}
void response(int fp)
{
        time_t rawtime;
        struct tm* timeinfo;
        time(&rawtime);
        timeinfo = localtime(&rawtime);
        char str[39] = "I am server. ";
        strcat(str,asctime(timeinfo));
        write(fp,str,sizeof(str));
}
void server_log(struct sockaddr_in* client_attr)
{
        char ip[16] = {'\0'};
        // 结构体的的数据不能直接显示,需要先转换成本机字节序
        inet_ntop(AF_INET,&client_attr->sin_addr.s_addr,ip,sizeof(ip));
        printf("connected by %s:%d\n",ip,ntohs(client_attr->sin_port));
}
服务器端代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
int main(int argc,char* argv[])
{
        if(argc < 3)
        {
                printf("incorrect parameter\n");
                return 1;
        }
        int client_fd = socket(AF_INET,SOCK_STREAM,0);
        struct sockaddr_in client_attr;
        memset(&client_attr,0,sizeof(client_attr));
        client_attr.sin_family = AF_INET;
        // 主机字节序转换成网络字节序
        client_attr.sin_port = htons((short)atoi(argv[2]));
        inet_pton(AF_INET,argv[1],&client_attr.sin_addr.s_addr);
        // client_attr.sin_addr.s_addr = 
        if(-1 == connect(client_fd,(struct sockaddr*)&client_attr,sizeof(client_attr)))
        {
                perror("connect ");
                return 1;
        }
        char res[39] = {'\0'};
        read(client_fd,res,sizeof(res));
        printf("buf = %s\n",res);
        close(client_fd);
        return 0;
}
自定义协议服务器端,多进程处理并发
#include <stdio.h>
// atoi函数
#include <stdlib.h>
// wait函数
#include <sys/wait.h>
// I/O函数
#include <unistd.h>
#include <time.h>
// socket 系列函数
#include <sys/socket.h>
// sockaddr_in 结构体
#include <netinet/in.h>
// hton 函数
#include <arpa/inet.h>
#include <signal.h>
// memset函数
#include <string.h>
// 服务器socket描述符
int server_fd = 0;
// 服务器连接属性
struct sockaddr_in server_attr;
// 客户端socket描述符
int client_fd = 0;
// 客户端连接属性
struct sockaddr_in client_attr;
// ctrl + c 关闭服务器监听
void sigint_handle(int);
//  回收子进程方法
void sigchld_handle(int);
// 响应客户端连接
void response(int);
// 服务器连接日志
void server_log(struct sockaddr_in*);
// 自定义写
int my_write(int fd,char* str);
// 自定义读
int my_read(int fd,char* str);
/* 自定义协议结构体 */
typedef struct{
        int bytes;
        char data[512];
}my_protocol;
int main(int argc,char* argv[])
{
        if(argc < 2)
        {
                printf("incorrect parameter\n");
                return 1;
        }
        if(SIG_ERR == signal(SIGINT,sigint_handle))
        {
                perror("signal error");
                return 1;
        }
        memset(&server_attr,0,sizeof(server_attr));
        memset(&client_attr,0,sizeof(server_attr));
        /*
         * 1. 创建socket
         * socket创建在内核中,是一个结构体
         * AF_INET : IPV4
         * SOCK_STREAM : TCP协议
         */
        server_fd = socket(AF_INET,SOCK_STREAM,0);
        if(server_fd == -1)
        {
                perror("sock error");
                return 1;
        }
        /*
         * 2. 调用bind函数将socket与地址(ip,port)进行绑定
         */
        server_attr.sin_family = AF_INET;
        // 转换位网络字节序
        server_attr.sin_port = htons((short)atoi(argv[1]));
        // 表示所有ip,也可以指定第一个ip,指定ip时要把主机字节序转换成网络字节序
        server_attr.sin_addr.s_addr = INADDR_ANY;
        // 绑定ip端口
        if(-1 == bind(server_fd,(struct sockaddr*)&server_attr,sizeof(server_attr)))
        {
                perror("bind error");
                return 1;
        }
        /*
         * 3. 调用listen函数启动监听,通知系统接收来自客户端的请求
         * 第二个参数代表客户端队列的长度
         * 执行成功后能用netstat查看
         */
        if(-1 == listen(server_fd,8))
        {
                perror("listen error");
                return 1;
        }
        socklen_t addrlen = sizeof(client_attr);
        /*
         * 4. 调用accept获得客户端连接,并返回一个新的socket文件描述符
         * 若没有客户端连接,此函数会阻塞,直到获得一个客户端连接
         */
        int pid = 0;
        // 注册SIGCHLD信号处理函数,回收子进程,避免僵尸进程的产生
        signal(SIGCHLD,sigchld_handle);
        while(1)
        {
                client_fd = accept(server_fd,(struct sockaddr*)&client_attr,&addrlen);
                if(client_fd <= 0)
                {
                        continue;
                }
                server_log(&client_attr);
                // 子线程处理I/O,达到并发的效果
                pid = fork();
                if(pid == 0)
                {
                        /*
                        * 5. 调用I/O函数(read/write)和连接的客户端进行双向的通信
                        */
                        response(client_fd);
                        close(client_fd);
                        break;
                }
                close(client_fd);
        }
        close(server_fd);
        return 0;
}
void sigint_handle(int sig)
{
        close(server_fd);
        exit(0);
}
void response(int fp)
{
        time_t rawtime;
        struct tm* timeinfo;
        char client_data[512];
        int read_len = 0;
        while(1)
        {
                memset(client_data,'\0',sizeof(client_data));
                time(&rawtime);
                timeinfo = localtime(&rawtime);
                if((read_len = my_read(fp,client_data)) == -1)
                {
                        break;
                }
                else if(read_len == 0)
                {
                        strcat(client_data,"incorrect data\n");
                }
                else
                {
                        strcat(client_data,"\n");
                        strcat(client_data,asctime(timeinfo));
                }
                if(my_write(fp,client_data) == -1)
                {
                        break;
                }
        }
}
void server_log(struct sockaddr_in* client_attr)
{
        char ip[16] = {'\0'};
        // 结构体的的数据不能直接显示,需要先转换成本机字节序
        inet_ntop(AF_INET,&client_attr->sin_addr.s_addr,ip,sizeof(ip));
        printf("connected by %s:%d\n",ip,ntohs(client_attr->sin_port));
}
int my_write(int fd,char* str)
{
        my_protocol p = {0};
        memset(&p.data,'\0',sizeof(p.data));
        strcpy(p.data,str);
        p.bytes = strlen(p.data);
        if(write(fd,&p,sizeof(p)) < 0)
        {
                return -1;
        }
        return 0;
}
int my_read(int fd,char* str)
{
        my_protocol p = {0};
        memset(&p.data,'\0',sizeof(p.data));
        int read_len = 0;
        read_len = read(fd,&p,sizeof(p));
        if(p.bytes != strlen(p.data) && read_len != 0)
        {
                return -1;
        }
        strcpy(str,p.data);
        return read_len;
}
//  回收子进程方法
void sigchld_handle(int sig)
{
        // signal(SIGCHLD,sigchld_handle);
        wait(NULL);
        printf("free\n");
}
自定义协议客户端
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
// 自定义写
int my_write(int fd,char* str);
// 自定义读
int my_read(int fd,char* str);
/* 自定义协议结构体 */
typedef struct{
        int bytes;
        char data[512];
}my_protocol;
int main(int argc,char* argv[])
{
        if(argc < 3)
        {
                printf("incorrect parameter\n");
                return 1;
        }
        int client_fd = socket(AF_INET,SOCK_STREAM,0);
        struct sockaddr_in client_attr;
        memset(&client_attr,0,sizeof(client_attr));
        client_attr.sin_family = AF_INET;
        // 主机字节序转换成网络字节序
        client_attr.sin_port = htons((short)atoi(argv[2]));
        inet_pton(AF_INET,argv[1],&client_attr.sin_addr.s_addr);
        // client_attr.sin_addr.s_addr = 
        if(-1 == connect(client_fd,(struct sockaddr*)&client_attr,sizeof(client_attr)))
        {
                perror("connect ");
                return 1;
        }
        char res[512];
        while(1)
        {
                memset(res,'\0',sizeof(res));
                scanf("%s",res);
                if(-1 == my_write(client_fd,res))
                {
                        perror("write");
                        break;
                }
                if(my_read(client_fd,res) <= 0)
                {
                        perror("read");
                        break;
                }
                printf("res = %s",res);
        }
        close(client_fd);
        return 0;
}
int my_write(int fd,char* str)
{
        my_protocol p = {0};
        memset(&p.data,'\0',sizeof(p.data));
        strcpy(p.data,str);
        p.bytes = strlen(p.data);
        if(write(fd,&p,sizeof(p)) < 0)
        {
                return -1;
        }
        return 0;
}
int my_read(int fd,char* str)
{
        my_protocol p = {0};
        memset(&p.data,'\0',sizeof(p.data));
        int read_len = 0;
        read_len = read(fd,&p,sizeof(p));
        if(p.bytes != strlen(p.data) && read_len != 0)
        {
                return -1;
        }
        strcpy(str,p.data);
        return read_len;
}
tcp自定义协议服务器端,多线程处理并发
#include <stdio.h>
// atoi函数
#include <stdlib.h>
// wait函数
#include <sys/wait.h>
// I/O函数
#include <unistd.h>
#include <time.h>
// socket 系列函数
#include <sys/socket.h>
// sockaddr_in 结构体
#include <netinet/in.h>
// hton 函数
#include <arpa/inet.h>
#include <signal.h>
// memset函数
#include <string.h>
// pthread库
#include <pthread.h>
// 服务器socket描述符
int server_fd = 0;
// 服务器连接属性
struct sockaddr_in server_attr;
// 客户端socket描述符
int client_fd = 0;
// 客户端连接属性
struct sockaddr_in client_attr;
// ctrl + c 关闭服务器监听
void sigint_handle(int);
//  回收子进程方法
void sigchld_handle(int);
// 响应客户端连接
void response(int);
// 服务器连接日志
void server_log(struct sockaddr_in*);
// 自定义写
int my_write(int fd,char* str);
// 自定义读
int my_read(int fd,char* str);
// 多线程处理并发函数
void* thread_handle(void* data);
/* 自定义协议结构体 */
typedef struct{
        int bytes;
        char data[512];
}my_protocol;
int main(int argc,char* argv[])
{
        if(argc < 2)
        {
                printf("incorrect parameter\n");
                return 1;
        }
        if(SIG_ERR == signal(SIGINT,sigint_handle))
        {
                perror("signal error");
                return 1;
        }
        memset(&server_attr,0,sizeof(server_attr));
        memset(&client_attr,0,sizeof(server_attr));
        /*
         * 1. 创建socket
         * socket创建在内核中,是一个结构体
         * AF_INET : IPV4
         * SOCK_STREAM : TCP协议
         */
        server_fd = socket(AF_INET,SOCK_STREAM,0);
        if(server_fd == -1)
        {
                perror("sock error");
                return 1;
        }
        /*
         * 2. 调用bind函数将socket与地址(ip,port)进行绑定
         */
        server_attr.sin_family = AF_INET;
        // 转换位网络字节序
        server_attr.sin_port = htons((short)atoi(argv[1]));
        // 表示所有ip,也可以指定第一个ip,指定ip时要把主机字节序转换成网络字节序
        server_attr.sin_addr.s_addr = INADDR_ANY;
        // 绑定ip端口
        if(-1 == bind(server_fd,(struct sockaddr*)&server_attr,sizeof(server_attr)))
        {
                perror("bind error");
                return 1;
        }
        /*
         * 3. 调用listen函数启动监听,通知系统接收来自客户端的请求
         * 第二个参数代表客户端队列的长度
         * 执行成功后能用netstat查看
         */
        if(-1 == listen(server_fd,8))
        {
                perror("listen error");
                return 1;
        }
        socklen_t addrlen = sizeof(client_attr);
        /*
         * 4. 调用accept获得客户端连接,并返回一个新的socket文件描述符
         * 若没有客户端连接,此函数会阻塞,直到获得一个客户端连接
         */
        pthread_t tid = 0;
        // 初始化线程属性,用来创建分离的线程,使子线程结束后自动回收
        pthread_attr_t thread_attr;
        pthread_attr_init(&thread_attr);
        pthread_attr_setdetachstate(&thread_attr,PTHREAD_CREATE_DETACHED);
        // 注册SIGCHLD信号处理函数,回收子进程,避免僵尸进程的产生
        signal(SIGCHLD,sigchld_handle);
        // 忽略SIGPIPE信号处理函数,避免由于客户端断开连接,并由于read发送SIGPIPE信号导致进程结>束
        signal(SIGPIPE,SIG_IGN);
        while(1)
        {
                client_fd = accept(server_fd,(struct sockaddr*)&client_attr,&addrlen);
                if(client_fd <= 0)
                {
                        continue;
                }
                server_log(&client_attr);
                // 子线程处理I/O,达到并发的效果
                pthread_create(&tid,&thread_attr,thread_handle,(void*)client_fd);
                        printf("child thread\n");
                        // break;
                // 线程是共享的进程资源,这里不要关闭,不然线程里面的也没了
                // close(client_fd);
        }
        // close(server_fd);
        sleep(3);
        return 0;
}
void sigint_handle(int sig)
{
        close(server_fd);
        close(client_fd);
        exit(0);
}
void response(int fp)
{
        time_t rawtime;
        struct tm* timeinfo;
        char client_data[512];
        int read_len = 0;
        while(1)
        {
                memset(client_data,'\0',sizeof(client_data));
                time(&rawtime);
                timeinfo = localtime(&rawtime);
                if((read_len = my_read(fp,client_data)) == -1)
                {
                        break;
                }
                else if(read_len == 0)
                {
                        strcat(client_data,"incorrect data\n");
                }
                else
                {
                        strcat(client_data,"\n");
                        strcat(client_data,asctime(timeinfo));
                }
                if(my_write(fp,client_data) == -1)
                {
                        break;
                }
        }
}
void server_log(struct sockaddr_in* client_attr)
{
        char ip[16] = {'\0'};
        // 结构体的的数据不能直接显示,需要先转换成本机字节序
        inet_ntop(AF_INET,&client_attr->sin_addr.s_addr,ip,sizeof(ip));
        printf("connected by %s:%d\n",ip,ntohs(client_attr->sin_port));
}
int my_write(int fd,char* str)
{
        my_protocol p = {0};
        memset(&p.data,'\0',sizeof(p.data));
        strcpy(p.data,str);
        p.bytes = strlen(p.data);
        if(write(fd,&p,sizeof(p)) < 0)
        {
                return -1;
        }
        return 0;
}
int my_read(int fd,char* str)
{
        my_protocol p = {0};
        memset(&p.data,'\0',sizeof(p.data));
        int read_len = 0;
        read_len = read(fd,&p,sizeof(p));
        if(p.bytes != strlen(p.data) && read_len != 0)
        {
                return -1;
        }
        strcpy(str,p.data);
        return read_len;
}
//  回收子进程方法
void sigchld_handle(int sig)
{
        // signal(SIGCHLD,sigchld_handle);
        wait(NULL);
        printf("free\n");
}
void* thread_handle(void* data)
{
        int fd = (int)data;
        /*
        * 5. 调用I/O函数(read/write)和连接的客户端进行双向的通信
        */
        response(fd);
        close(fd);
        return (void*)NULL;
}

udp编程

udp编程模型

  • 客户端调用序列

    • 调用socket函数创建套接字

    • 调用bind连接服务器端(这一步可以不要)

    • 调用sendto/readfrom与服务器端通讯

    • 调用close关闭套接字

  • 服务器端调用序列

    • 调用socket函数创建套接字

    • 调用bind绑定本地地址和端口

    • 调用sendto/readfrom与客户端通讯

    • 调用close关闭套接字

数据传输

发送数据

ssize_t send(int sockfd,const void* buf,size_t nbytes,int flag);

  • 返回:成功返回发送字节数,出错返回-1

  • 功能:发送数据,需要在发送数据前已经建立的连接,多用于TCP,用于UDP时要使用connect函数先建立连接
    `ssize_t sendto(int sockfd,const void buf,size_t nbytes,int flag,const struct sockaddr

desaddr,socklen_t destlen);`

  • 返回:成功返回发送的字节数,出错返回-1

  • 功能:发送数据,需要指定发送方(desaddr参数)

  • sockaddr:目的地的相关信息
    ssize_t sendmsg(int sockfd,const struct msghdr* msg,int flag);

  • 返回:成功返回发送的字节数,出错返回-1

  • 头文件

#include <sys/socket.h>
  • msghdr结构体

struct msghdr
{
        void* msg_name;// optional address
        socklen_t msg_namelen;// address size in bytes
        struct iovec* msg_iov;// array of I/O buffers
        int msg_iovlen;// number of elements in array
        void* msg_control;// ancillary data
        socklen_t msg_controllen;// number of ancillary bytes
        int msg_flags;// flag for received message
};
接收数据

ssize_t recv(int sockfd,void* buf,size_t nbytes,int flag);

  • 功能:接收数据,不能获得发送方的信息,
    ssize_t recvfrom(int sockfd,void*restrict buf,size_t len,int flag,struct sockaddr*restrict addr,socklen_t*restrict addrlen);

  • 功能:除了能获取数据外,还可以获得发送方的信息,用于在之后对指定的发送方发送数据
    ssize_t recvmsg(int sockfd,struct msghdr* msg,int flag);

  • 头文件

#include <sys/socket.h>

示例

服务器端
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#include <pthread.h>
typedef struct
{
        struct sockaddr_in attr;
        char data[512];
}thread_data;
int server_fd = 0;
void* thread_handle(void* client);
void server_log(struct sockaddr_in* attr);
int main(int argc,char* argv[])
{
        if(argc < 2)
        {
                printf("incorrect parametr\n");
                return 1;
        }
        /*
         * 1. 创建socket
         * SOCK_DGRAM : 使用UDP传输
         */
        server_fd = socket(AF_INET,SOCK_DGRAM,0);

        /*
         * 2. 设置服务器选项,SO_REUSEADDR,会先解除之前的绑定,在绑定一次,避免重启时因为连接还
没有断开导致的bind报错
         */
        setsockopt(server_fd,SOL_SOCKET,SO_REUSEADDR,(void*)NULL,0);

        /*
         * 3. 设置服务器选项,SO_SNDTIMEO,发送客户端消息的超时时间,避免子线程阻塞
         */
        struct timeval time_out = {3,0};
        // setsockopt(server_fd,SOL_SOCKET,SO_RCVTIMEO,(char*)&time_out,sizeof(time_out));
        setsockopt(server_fd,SOL_SOCKET,SO_SNDTIMEO,(char*)&time_out,sizeof(time_out));

        /*
         * 4. 设置绑定的ip及端口信息
         */
        struct sockaddr_in server_attr;
        server_attr.sin_family = AF_INET;
        server_attr.sin_port = htons((short)atoi(argv[1]));
        server_attr.sin_addr.s_addr = INADDR_ANY;

        /*
         * 5. 绑定本地ip,port
         */
        bind(server_fd,(struct sockaddr*)&server_attr,sizeof(server_attr));
        // 存放客户端信息
        thread_data* client;
        // 存放子线程id
        pthread_t tid = 0;
        // 存放子线程属性,用以设置以分离模式启动子线程
        pthread_attr_t thread_attr;
        memset(&thread_attr,0,sizeof(thread_attr));
        pthread_attr_setdetachstate(&thread_attr,PTHREAD_CREATE_DETACHED);
        // 上次是否读取成功的标识,用以判断是否需要重新申请内存
        int recv_flag = 0;
        // 连接信息结构体的大小
        socklen_t addrlen = sizeof(struct sockaddr_in);
        while(1)
        {
                /*
                * 6. 获得客户端发送的数据与客户端信息,用于与客户端通信
                */
                if(recv_flag == 0)
                {
                        client = malloc(sizeof(thread_data));
                }
                memset(client,0,sizeof(struct sockaddr_in));
                if(0 >= recvfrom(server_fd,client->data,sizeof(client->data),0,(struct sockaddr*)&client->attr,&addrlen))
                {
                        // 超时或者读取失败则继续下一次等待,设置标识,避免下次重新申请内存
                        printf("time out\n");
                        recv_flag = 1;
                        continue;
                }
                server_log(&client->attr);
                /*
                * 7. 使用子线程处理并发
                */
                pthread_create(&tid,&thread_attr,thread_handle,(void*)client);
                recv_flag = 0;
        }
        return 0;
}
void* thread_handle(void* client)
{
        thread_data* t = (thread_data*)client;
        char thread_id[20] = {'\0'};
        sprintf(thread_id,"%lu",pthread_self());
        // 连接信息结构体的大小
        strcat(t->data,"\n;thread_id = ");
        strcat(t->data,thread_id);
        sendto(server_fd,t->data,sizeof(t->data),0,(struct sockaddr*)&t->attr,sizeof(t->attr));
        // 释放动态申请的内存
        free(client);
        return NULL;
}
void server_log(struct sockaddr_in* attr)
{
        char ip[16] = {'\0'};
        inet_ntop(AF_INET,&attr->sin_addr.s_addr,ip,sizeof(ip));
        printf("connect from %s handle by %lu\n",ip,pthread_self());
}
udp客户端
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#include <pthread.h>
int main(int argc,char* argv[])
{
        if(argc < 2)
        {
                printf("incorrect parametr\n");
                return 1;
        }
        /*
         * 1. 创建socket
         * SOCK_DGRAM : 使用UDP传输
         */
        int client_fd = socket(AF_INET,SOCK_DGRAM,0);


        /*
         * 3. 设置服务器的ip及端口信息
         */
        struct sockaddr_in server_attr;
        server_attr.sin_family = AF_INET;
        server_attr.sin_port = htons((short)atoi(argv[2]));
        inet_pton(AF_INET,argv[1],&server_attr.sin_addr.s_addr);
        char res[512] = {'\0'};
        while(1)
        {
                scanf("%s",res);
                if(sendto(client_fd,res,sizeof(res),0,(struct sockaddr*)&server_attr,sizeof(server_attr)) <= 0)
                {
                        perror("sendto");
                        break;
                }
                if(recv(client_fd,res,sizeof(res),0) <= 0)
                {
                        perror("recv");
                        break;
                }
                printf("%s\n",res);
        }
        return 0;
}

域名解析函数

hostent结构体

struct hostent
{
        char* h_name;// 主机名
        char** h_aliases;// 别名,字符串数组
        int h_addrtype;// 协议类型
        int h_length;// 网络地址大小
        char** h_addr_list;// 指向网络地址的指针
};

函数

struct hostent* gethostent(void);
struct hostent* gethostbyname(const char* hostname);
void sethostent(int stayopen);
void endhostent(void); // 与hostent一起使用

  • 头文件

#include <netdb.h>

示例

if((hptr = gethostbyname("www.jinblog.com")) == NULL)
{
        fprintf(stderr,"gethostbyname call faile. %s\n",hsterror(h_errno));
        return 1;
}
printf("official name:%s\n",hptr->h_name);
for(pptr = hptr->h_aliases;*pptr != NULL;pptr++)
{
        printf("\talias:%s\n",*pptr);
}
if(hptr->h_addrtype != AF_INET)
{
        fprintf(stderr,"invalid address type %d\n",hptr->h_addrtype);
}
pptr = hptr->h_addr_list;
for(;*pptr != NULL;pptr++)
{
        printf("\taddress:%s\n",inet_ntop(hptr->h_addrtype,*pptr,str,sizeof(str)));
}
打印
#include <stdio.h>
#include <string.h>
#include <netdb.h>
#include <arpa/inet.h>
void print_host(struct hostent* host);
int main(void)
{
        printf("----------gethostent start--------\n");
        struct hostent* host;
        while((host = gethostent()) != NULL)
        {
                print_host(host);
        }
        endhostent();
        printf("----------gethostent  stop--------\n");
        
        printf("\n\n----------gethostentbyname start--------\n");

        if((host = gethostbyname("jinblog.com")) != NULL)
        {
                print_host(host);
        }

        printf("----------gethostentbyname  stop--------\n");
        return 0;
}
void print_host(struct hostent* host)
{
        printf("host : %s\n",host->h_name);
        int i = 0;
        while(host->h_aliases[i] != NULL)
        {
                printf("\talias : %s\n",host->h_aliases[i]);
                i++;
        }
        switch(host->h_addrtype)
        {
                case AF_INET:
                        printf("address type : AF_INET\n");
                        break;
                case AF_INET6:
                        printf("address type : AF_INET6\n");
                        break;
                case AF_UNIX:
                        printf("address type : AF_UNIX\n");
                        break;
                default:
                        printf("address type : unknown\n");
                        break;
        }
        printf("length : %d\n",host->h_length);
        i = 0;
        char ip[32] = {'\0'};
        while(host->h_addr_list[i] != NULL)
        {
                inet_ntop(host->h_addrtype,host->h_addr_list[i],ip,sizeof(ip));
                printf("\tip : %s",ip);
                i++;
                memset(ip,'\0',sizeof(ip));
        }
        printf("\n\n\n");
}

广播

  • 实现一对多的通讯

  • 通过向广播地址发送数据报文实现

套接字选项

  • 套接字选项用于修饰套接字以及其底层通讯协议的各种行为。函数setsockopt和getsockopt可以查看和设置套接字的各种选项
    int getsockopt(int sockfd,int optname,void* optval,socklen_t* optlen);

int setsockopt(int sockfd,int optname,void* optval,socklen_t* optlen);

SO_BROADCAST选项

  • SO_BROADCAST选项控制着UDP套接字是否能够发送广播数据报,选项的类型位int,非零意味着是,注意,只有UDP套接字可以使用这个选项,TCP是不能使用广播的

int opt = 1;
if((sockfd = sock(AF_INET,SOCK_DGRAM,0)) < 0)
{
        // 错误处理
}
if(setsockopt(sockfd,SOL_SOCKET,SO_BROADCAST,&opt,sizeof(int)) < 0)
{
        // 错误处理
}

SO_SNDBUF和SO_RCVBUF选项

  • 每个套接字有一个发送缓冲区和接收缓冲区,这两个缓冲区由底层协议使用,接收缓冲区存放由协议接收的数据直到被应用程序读走,发送缓冲区存放应用写出的数据直到被协议发送出去。SO_SNDBUF和SO_RCVBUF选项分别控制发送和接收缓冲区的大小,他们类型为int,以字节为单位

if((sockfd = sock(AF_INET,SOCK_DGRAM,0)) < 0)
{
        // 错误处理
}
int opt = 0;
if(getsockopt(sockfd,SOL_SOCKET,SO_SNDBUF,&opt,sizeof(opt)) < 0)
{
        // 错误处理
}
opt += 888;
if(setsockopt(sockfd,SOL_SOCKET,SO_SNDBUF,&opt,sizeof(opt)) < 0)
{
        // 错误处理
}

广播地址

  • 如果用{netID,subnetID,hostID}({网络id,子网id,主机id})标识IPV4地址,那么有四类的广播地址,我们用-1表示所有比特都为1的字段

  • 子网广播地址:{netID,subnetID,-1},这类地址编排指定子网上的所有接口,例如,如果我们对B类地址192.168采用8为子网id,那么192.168.2.255将是192.168.2子网上的所有接口的子网广播地址。路由器不转发这类广播

  • 全部子网地址:{netID,-1,-1}。这类广播地址编排指定网络上的所有子网。如果说这类地址被使用过的话,那么现在已很少见了

  • 受限广播地址{-1,-1,-1}或255.255.255.255。路由器从不转发目的地址为255.255.255.255的IP报数据。

示例

广播服务器端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
int main(int argc,char* argv[])
{
        if(argc < 3)
        {
                printf("incrrect parameter\n");
                return 1;
        }
        /*
         * 1. 创建socket,要创建UDP类型的socket,广播包是用UDP发送的
         */
        int server_fd = socket(AF_INET,SOCK_DGRAM,0);

        /*
         * 2. 设置要发送到的广播域和port
         */
        struct sockaddr_in des_sock_attr;
        des_sock_attr.sin_family = AF_INET;
        // 注意argv[1]是广播地址
        inet_pton(AF_INET,argv[1],&des_sock_attr.sin_addr.s_addr);
        des_sock_attr.sin_port = htons((short)atoi(argv[2]));

        /*
         * 2. 设置socket选项,使socket可以发送广播报
         */
        int opt = 1;
        setsockopt(server_fd,SOL_SOCKET,SO_BROADCAST,&opt,sizeof(int));
        char buf[512] = {'\0'};
        int i = 0;
        while(1)
        {
                sprintf(buf,"message %d\n",i);
                if(0 >= sendto(server_fd,buf,sizeof(buf),0,(struct sockaddr*)&des_sock_attr,sizeof(des_sock_attr)))
                {
                        perror("sendto");
                        continue;
                }
                printf("broadcast : %d\n",i);
                sleep(3);
                i++;
                memset(buf,'\0',sizeof(buf));
        }
        return 1;
}
广播客户端
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
int main(int argc,char* argv[])
{
        if(argc < 2)
        {
                printf("incrrect parameter\n");
                return 1;
        }
        /*
         * 1. 创建socket,要创建UDP类型的socket,广播包是用UDP发送的
         */
        int client_fd = socket(AF_INET,SOCK_DGRAM,0);
        /*
         * 2. 设置socket,ip,port等属性
         */
        struct sockaddr_in client_attr;
        client_attr.sin_port = htons((short)atoi(argv[1]));
        client_attr.sin_addr.s_addr = INADDR_ANY;
        client_attr.sin_family = AF_INET;

        /*
         * 3. 设置 SO_REUSEADDR,避免bind报错
         */
        setsockopt(client_fd,SOL_SOCKET,SO_REUSEADDR,(void*)NULL,0);
        /*
         * 4. bind绑定ip端口
         */
        bind(client_fd,(struct sockaddr*)&client_attr,sizeof(client_attr));

        /*
         * 5. 接收广播报数据
         */
        struct sockaddr_in server_attr;
        char buf[512] = {'\0'},ip[16] = {'\0'};
        socklen_t addrlen = sizeof(server_attr);
        while(1)
        {
                if(recvfrom(client_fd,buf,sizeof(buf),0,(struct sockaddr*)&server_attr,&addrlen) <= 0)
                {
                        perror("recvfrom");
                        return 1;
                }
                inet_ntop(AF_INET,&server_attr.sin_addr.s_addr,ip,sizeof(ip));
                printf("server ip : %s;\nbroadcast data : %s\n\n",ip,buf);
        }
        return 0;
}

下一篇:http://jinblog.com/archives/868.html

linux编程学习 04 进程间通信

进程间通信概述

  • 数据传输:一个进程需要将它的数据发送给另一个进程,发送的数据量在一个字节到几兆字节之间

  • 共享数据:多个进程想要操作共享数据,一个进程对共享数据的修改,别的进程应该立刻看到

  • 通知事件:一个进程需要向另一个或一组进程发送消息,通知他们发生了某种事件(如进程终止时要通知父进程)

  • 资源共享:多个进程之间共享同样的资源。为了做到这一点,需要内核提供锁和同步机制。

  • 进程控制:有些进程希望完全控制另一个进程的执行(如debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能知道他的状态和改变

进程间通信的变革

  • linux进程间通信(IPC)由以下几个部分发展而来

    • 早期unix进程间通信

    • 基于System V进程间通信

    • 基于socket进程间通信和POSIX进程间通信

  • unix进程间通信的方式包括:管道,FIFO,信号

  • System V进程间通信方式包括:System V消息队列,System V信号灯,System V共享内存

  • POSIX进程间通信包括:POSIX消息队列,POSIX信号灯,POSIX共享内存

现代的进程间通信的方式

  • 管道(pipe)和命名管道(FIFO)

  • 信号(signal)

  • 消息队列

  • 共享内存

  • 信号量

  • 套接字(socket)

管道通信

  • 管道是针对于本地计算机的两个进程之间的通信而设计的通信方法,管道建立后,实际获得两个文件描述符:一个用于读取而另一个用于写入

  • 最常见的IPC通信机制,通过pipe系统调用

  • 管道是单工的,数据只能向一个方向流动,需要双向通信时,需要建立起两个管道

  • 数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区头部读出数据

  • 本质上是在内核中的一个缓存

管道分类

  • 匿名管道

    • 在关系进程中进行(父进程和子进程,兄弟进程之间)

    • 由pipe系统调用,管道由父进程建立

    • 管道位于内核空间,其实是一块缓存

  • 命名管道

    • 两个没有任何关系的进程之间通信可通过命名管道进行数据传输,本质是内核中的一块缓存,另在文件系统中以一个特殊的设备文件(管道文件)存在

    • 通过系统调用mkfifo创建

匿名管道创建

int pipe(int fp[2]);

  • 返回:成功返回0,出错返回-1

  • 头文件:

#include <unistd.h>
  • 两个文件描述符数组

    • fd[0]:为pipe的读端

    • fd[1]:为pipe的写端

    • fd[0]用于读取管道,fd[1]用于写入管道

管道读写

管道主要用于不同进程间通信,实际上,通常先创建一个管道,再通过fork函数创建一个子进程

示例

基本使用
int main(void)
{
        int fp[2];
        if(pipe(fp) == -1)
        {
                perror("create pipe error");
                return 1;
        }
        int pid = fork();
        int i = 0;
        if(pid == -1)
        {
                perror("fork error");
                return 1;
        }
        else if(pid > 0)
        {
                // 父进程写入,先关闭管道读端
                close(fp[0]);
                i = 8;
                if(sizeof(int) != write(fp[1],(void*)&i,sizeof(int)))
                {
                        perror("first write error");
                        return 1;
                }
                i = 88;
                if(sizeof(int) != write(fp[1],(void*)&i,sizeof(int)))
                {
                        perror("second write error");
                        return 1;
                }
                wait(NULL);
                close(fp[1]);
        }
        else if(pid == 0)
        {
                // 子进程读,先关闭管道写端
                close(fp[1]);
                int j = 0;
                if(-1 == read(fp[0],(void*)&j,sizeof(int)))
                {
                        perror("first read error");
                        return 1;
                }
                printf("first read;i = %d\n",j);
                if(-1 == read(fp[0],(void*)&j,sizeof(int)))
                {
                        perror("second read error");
                        return 1;
                }
                printf("second read;i = %d\n",j);
                close(fp[0]);
        }

        return 0;
}
达到 cat /etc/passwd | grep root 的效果
#include <stdio.h>
#include <unistd.h>
char* command1[] = {"cat","/etc/passwd",NULL};
char* command2[] = {"grep","root",NULL};
int main(void)
{
        int fp[2];
        // 备份标准输出
        int stdout_copy = dup(STDOUT_FILENO);
        if(-1 == pipe(fp))
        {
                perror("pipe error");
                return 1;
        }
        int pid = fork();
        if(pid == -1)
        {
                perror("first fork error");
                return 1;
        }
else if(pid == 0)// 读取内容进程
        {
                // 关闭读端
                close(fp[0]);
                // cat命令默认从标准输出读取数据,使用dup2命令把标准输出转换成管道的输入端
                dup2(fp[1],STDOUT_FILENO);
                close(fp[1]);
                if(execvp(command1[0],command1) == -1)
                {
                        // 显示出错信息前先把标准输出还原
                        dup2(stdout_copy,STDOUT_FILENO);
                        printf("fail to execute %s",command1[0]);
                        perror("\n");
                        return 1;
                }
        }
        else if(pid > 0)
        {
                pid = fork();
                if(pid == -1)
                {
                        perror("second fork error");
                        return 1;
                }
                else if(pid == 0)// 过滤内容进程
                {
                        // 关闭写端
                        close(fp[1]);
                        // 和上面进程一样,把标准输入转换成管道出口
                        dup2(fp[0],STDIN_FILENO);
                        close(fp[0]);
                        if(execvp(command2[0],command2) == -1)
                        {
                                dup2(stdout_copy,STDOUT_FILENO);
                                printf("fail to execute %s",command2[0]);
                                perror("\n");
                                return 1;
                        }
                }
                else// 主进程
                {
                        // 回收进程资源
                        printf("\n");
                        wait(NULL);
                        // 关闭
                        close(fp[0]);
                        close(fp[1]);
                }
        }
        return 0;
}

管道的读写特性

  • 通过打开两个管道来创建一个双向的管道

  • 管道是阻塞性的,当进程从管道中读取数据,若没有数据进程会阻塞

  • 当一个进程往管道中不断写入数据但是没有进程去读取数据,此时只要管道没有满是可以的,但若管道放满数据则会报错

  • 不完整管道

    • 当读一个写端已经被关闭的管道时,在所有数据被读取后,read返回0,以表示到达了文件末尾

    • 如果写一个读端已被关闭的管道,则产生信号SIGPIPE,如果忽略该信号或捕捉该信号并从处理程序返回,则write返回-1,同时errno设置为EPIPE

  • 示例

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void func1(void);
void func2(void);
int main(void)
{
        func2();
        return 0;
}
/* 读取写端被关闭的管道  */
void func1(void)
{
        int fp[2];
        if(-1 == pipe(fp))
        {
                return;
        }
        int pid = fork();
        if(pid == -1)
        {
                perror("fork error");
                return;
        }
        else if(pid == 0)
        {
                close(fp[0]);
                write(fp[1],"I am child precess\n",19);
                close(fp[1]);
        }
        else
        {
                sleep(3);
                close(fp[1]);
                char c = '\0';
                while(1)
                {
                        // 写端被关闭,读完管道中的数据后返回0,不会阻塞
                        if(0 == read(fp[0],&c,1))
                        {
                                break;
                        }
                        printf("%c",c);
                }
                close(fp[0]);
        }
}
void sigpipe_handle(int sig)
{
        printf("I am sigpipe_handle\n");
        return;
}
/* 往读端被关闭的管道写入数据 */
void func2(void)
{
        int fp[2];
        pipe(fp);
        close(fp[1]);
        signal(SIGPIPE,sigpipe_handle);
        if(-1 == write(fp[0],"8",1))
        {
                perror("write pipe error");
                return;
        }
        close(fp[0]);
}

标准库中的管道操作

FILE* popen(const char* cmdstring,const char* type);

  • 返回值:成功返回文件指针,出错返回NULL
    int pclose(FILE *fp);

  • 返回值:cmdstring的终止状态,出错返回-1

  • 头文件

#include <stdio.h>
  • 使用popen创建的管道必须使用pclose关闭。其实popen/pclose和标准文件输入/输出流中的fopen/fclose十分相似

  • 封装管道的常用操作

  • 示例

int main(void)
{
        // 读
        FILE *f = popen("cat /etc/passwd","r");
        char str[1024] = {'\0'};
        while(NULL != fgets(str,1024,f))
        {
                // printf("%s",str);
        }
        pclose(f);
        // 写
        f = popen("wc -l","w");
        fprintf(f,"/etc/passwd\n/etc/shadow");
        pclose(f);
        return 0;
}

命名管道FIFO创建

int mkfifo(const char* pathname,mmode_t mode);

  • 返回:成功返回0,出错返回-1

  • 头文件

#include <sys/types.h>
#include <sys/stat.h>
  • 只要对FIFO有适当访问权限,FIFO可用在任何两个没有任何关系的进程之间通信。

  • 本质是内核的一块缓存,另外在文件系统中以一个特殊的设备文件(管道文件)存在

  • 在文件系统中只有一个索引块存放文件的路径,没有数据块,所有数据存放在内核中。

  • 命名管道必须读和写同时打开,否则单独读或者单独写会引发阻塞

  • 命名mkfifo创建命名管道(命令内部调用mkfifo函数)

  • 对FIFO的操作与操作普通文件一样

  • 一旦已经用mkfifo创建了一个FIFO,就可以用open打开它,一般文件I/O函数(open,close,write,read,unlink等)都可用于FIFO

  • FIFO相关出错信息

    • EACCES(无存取权限)

    • EEXIST(指定文件不存在)

    • ENAMETOOLONG(路径名太长)

    • ENOENT(包含的目录不存在)

    • ENOSPC(文件系统剩余空间不足)

    • ENOTDIR(文件路径无效)

    • EROFS(指定文件存在于只读文件系统中)

  • 示例

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc,char* argv[])
{
        if(argc < 2)
        {
                printf("incorret parameter\n");
                return 1;
        }
        // 创建管道文件
        char *path = "/tmp/fifo_test";
        mkfifo("/tmp/fifo_test",0777);
        int f = open(path,O_RDWR);
        char str[8] = {'\0'}; 
        // 可以启动两个,一个读取一个写入
        if(strcmp("w",argv[1]) == 0)
        {
                scanf("%s",str);
                write(f,str,8);
        }
        else if(strcmp("r",argv[1]) == 0)
        {
                read(f,str,8);
                printf("str = %s\n",str);
        }
        close(f);
        return 0;
}

System V IPC

概述

  • unix系统存在信号,管道和命名通道等基本进程间通信机制

  • System V引入了三种高级进程间通信机制

    • 消息队列,共享内存和信号量

  • IPC对象(消息队列,共享内存和信号量)存在于内核中而不是文件系统中,由用户控制释放(用户管理IPC对象的生命周期),不像管道的释放由内核控制

  • IPC对象通过其标识符来引用和访问,所有IPC对象在内核空间中有唯一性标识,在用户空间中的唯一性标识称为key

  • linux IPC继承自System V IPC

System V IPC对象访问

  • IPC对象是全局对象

    • 可用ipcs,ipcrm等命令查看或删除

  • 每个IPC对象都由get函数创建

    • msgget,shmget,semget

    • 调用get函数时必须指定关键字key

IPC对象的权限和所有者结构体

struct ipc_perm
{
        uid_t uid;// owner's effective user id
        gid_t gid;// owner's effective group id
        uid_t cuid;// creator's effective user id
        gid_t cgid;// creator's effective group id
        mode_t mode;// access mode
        ......
};

消息队列

  • 消息队列是内核中的一个链表

  • 用户进程将数据传输到内核后,内核重现添加一些如用户id,组id,读写进程的id和优先级等相关信息后并打包成一个数据包称为消息

  • 允许一个或多个进程往消息队列中写消息读消息,但一个消息只能被一个进程读取,读取完毕后就自动删除

  • 消息队列具有一定的FIFO的特性,消息可以按照顺序发送到队列中,也可以以几种不同的方式从队列中读取。每一个消息队列在内核中用一个唯一的IPC标识id表示。

  • 消息队列的实现包括创建和打开队列,发送消息,读取消息和控制消息队列四种操作

消息队列属性
struct msqid_ds{
        struct ipc_perm msg_perm;
        msgqnum_t msg_qnum;// of message on queue
        msglen_t msg_qbytes;// max of bytes on queue 最大消息字节数
        pid_t msg_lspid;// pid of last msgsnd() 最后一次发送消息的进程的pid
        pid_t msg_lrpid;// pid of last msgrcv() 最后一次接受消息的进程的pid
        time_t msg_stime;// last msgsnd time 最后发送消息的时间
        time_t msg_ctime;// last change time 最后一次改变的时间
......
};
打开或创建消息队列

int msgget(key_t key,int flag);

  • 返回:成功返回内核中消息队列的表示id,出错返回-1

  • 头文件

#include <sys/msg.h>
  • 参数

    • key:用户指定的消息队列的键值

    • flag:IPC_CREAT,IPC_EXCL等权限组合

  • 若创建消息队列,key可指定键值,也可将之设置为IPC_PRIVATE。若打开进行查询,则key不能为0必须是一个非0的值否则是查询不到的

消息队列控制

int msgctl(int msgid,int cmd,struct msqid_ds *buf)

  • 返回:成功返回0,出错返回-1

  • 头文件

#include <sys/msg.h>
  • 参数

    • msgid:消息队列的id

    • buf:消息队列属性指针

    • cmd:

      • IPC_STAT:获取消息队列属性,取此队列的msqid_ds结构,并将其存放在buf指向的结构体中

      • IPC_SET:设置属性,按由buf指向的结构体中的值,设置此队列相关的结构中的字段

      • IPC_RMID:删除队列,从系统中删除该消息队列以及仍在该队列上的所有数据

发送消息

int msgsnd(int msgqid,const void* ptr,size_t nbytes,int flag);

  • 返回:成功返回0,出错返回-1

  • ptr 结构

struct mymesg{
        long mytype; // positive message type
        char mtext[512]; // message data of length nbytes
...
};
  • nbytes:指定消息的大小,不包括mtype的大小

  • mtype是指消息的类型,他由一个整数来代表,并且他只可能是大于0的整数

  • mtext是消息数据本身

  • 在linux中消息的最大长度是4056个字节,其中包括mtype,他占四个字节

  • 结构体mymesg用户可自定义,但第一个成员必须是mtype

  • 参数flag

    • 0:阻塞

    • IPC_NOWAIT:类似于文件I/O的非阻塞I/O标志

    • 若消息队列已满(或是队列中消息总数等于系统限制值,或队列中的字节总数等于系统限制值),则指定IPC_NOWAIT使得msgsnd立即出错返回EAGAIN。如果指定0,则进程

      • 阻塞直到有空间可以容纳要发送的消息

      • 或从系统中删除了此队列

      • 或捕捉到了一个信号,并从信号处理程序返回

接受消息

size_t msgrcv(int msgqid,void* ptr,size_t nbytes,long type,int flag);

  • 返回:成功返回消息的数据部分长度,出错返回-1

  • 头文件

#include <sys/msg.h>
  • 参数

    • msgqid:消息队列id

    • ptr:指向存放消息的缓存

    • nbytes:消息缓存的大小,不包括mtype的大小。计算方式

      • nbytes = sizeof(struct mymesg) - sizeof(long)

    • type:消息类型

      • type == 0:获取队列消息中的第一个消息

      • type > 0:获取消息队列中类型为type的第一个消息

      • type < 0:获取消息队列中类型小于或等于type绝对值的消息(有多个就取类型最小的一个)

    • flag:0或IPC_NOWAIT

消息示例

#include <sys/msg.h>
#include <stdio.h>
#include <string.h>
void send_message(int);
void receive_message(int);
/* 消息结构体,注意第一个元素必须是type */
typedef struct{
        long type; // 必须大于1,否则发送消息失败
        int id;
        char name[8];
}my_message;
int main(int argc,char* argv[])
{
        if(argc < 3)
        {
                printf("incorrect parameter\n");
                return 1;
        }
        // 可以用ftok生成key,注意argv[2]必须是一个实际存在的路径,不然函数返回-1
        // key_t k = ftok(argv[2],0);
        // 直接使用数字 
        key_t k = atoi(argv[2]);
        // 打开/创建消息队列
        int msgqid = 0;
        if((msgqid = msgget(k,IPC_CREAT | 0777)) == -1)
        {
                perror("msgget error");
                return 1;
        }
        if(strcmp("send",argv[1]) == 0)
        {
                send_message(msgqid);
        }
        else if(strcmp("receive",argv[1]) == 0)
        {
                receive_message(msgqid);
        }
        else
        {
                printf("incorrect parameter\n");
                return 1;
        }
        // 删除队列,程序结束后消息队列还是会存在,不用一定要接的删除
        if(msgctl(msgqid,IPC_RMID,NULL) == -1)
        {
                perror("remove message queue error");
                return 1;
        }
        return 0;
}
/* 发送消息方法 */
void send_message(int msgqid)
{
        my_message m = {0,0,{'\0'}};
        int type = 1;
        // 消息的大小,注意要减去type的大小
        size_t bytes = sizeof(my_message) - sizeof(long);
        struct msqid_ds ds;
        int c;
        while(1)
        {
                printf("input id name\n");
                scanf("%d %7s",&m.id,m.name);
                m.type = type;
                if(msgsnd(msgqid,(void*)&m,bytes,0) == -1)
                {
                        perror("msgsnd error");
                        break;
                }
                type++;
                if(-1 == msgctl(msgqid,IPC_STAT,&ds))
                {
                        perror("msgctl error");
                }
                else
                {
                        printf("msgqnum = %u\n",(unsigned int)ds.msg_qnum);
                }
                // 重置name字符数组
                memset(m.name,'\0',8);
                // 清空标准输入的缓冲区的内容
                while ((c=getchar()) != '\n' && c!=EOF);
        }
}
/* 接受消息方法 */
void receive_message(int msgqid)
{
        my_message m;
        // 消息的大小,注意要减去type的大小
        size_t bytes = sizeof(my_message) - sizeof(long);
        struct msqid_ds ds;
        while(1)
        {
                if(msgrcv(msgqid,(void*)&m,bytes,0,0) == -1)
                {
                        perror("msgrcv error");
                        break;
                }
                else
                {
                        printf("type = %d;id = %d;name = %s\n",(int)m.type,m.id,m.name);
                }
                if(-1 == msgctl(msgqid,IPC_STAT,&ds))
                {
                        perror("msgctl error");
                }
                else
                {
                        printf("msgqnum = %u\n",(unsigned int)ds.msg_qnum);
                }
        }
}

共享内存

  • 共享内存区域是被多个进程共享的一部分物理内存

  • 多个进程都可把该共享内存映射到自己的虚拟内存空间。所有用户空间的进程若要操作共享内存,都要将其映射到自己的虚拟内存空间中,通过映射的虚拟内存空间地址去操作共享内存,从而达到进程间的数据通信

  • 共享内存是进程间共享数据的一种最快的方法,一个进程向内存区域写入数据,共享这个区域的所有内存就可以立刻看到其中的内容

  • 本身不提供同步机制,可通过信号量进行同步

  • 提高数据处理效率,一种效率最高的IPC机制

共享内存属性
struct shmid_ds{
        struct ipc_perm shm_perm;
        size_t shm_segsz;// size of segment in bytes 共享内存区大小
        pid_t shm_lpid;// pid of last shmop 最后一次操作共享内存的进程id
        pid_t shm_cpid;// pid of creator 创建共享内存的进程的id
        shmatt_t shm_nattch;// number of current attaches 和当前共享内存成功映射的内存数量
        time_t shm_atime;// last attach time 最后成功映射的时间
        time_t shm_dtime;// last detach time 最后解除映射的时间
        time_t shm_ctime;// last change time 最后一次改变的时间
......
};
共享内存使用步骤
  • 使用shmget函数创建共享内存

  • 使用shmat函数映射共享内存,将这段创建的共享内存映射到具体的进程虚拟空间中

创建共享内存

int shmget(key_t key,size_t size,int shmflg);

  • 返回:成功返回内核中共享内存的标识id,失败返回-1

  • 头文件

#include <sys/shm.h>
  • 参数

    • key:用户指定的共享内存键值

    • size:共享内存大小

    • shmflg:IPC_CREAT,IPC_EXCL等权限组合

  • errno

    • EINVAL(无效的内存段大小)

    • EEXIST(内存段已经存在,无法创建)

    • EIDRM(内存段已经被删除)

    • ENOENT(内存段不存在)

    • EACCES(权限不够)

    • ENOMEM(没有足够的内存来创建内存段)

共享内存控制

int shmctl(int shmid,int cmd,struct shmid_ds* buf);

  • 返回:成功返回0,出错返回-1

  • 头文件

#include <sys/shm.h>
  • 参数

    • shmid:共享内存id

    • buf:共享内存属性指针

    • cmd

      • IPC_STAT 获取共享内存段属性

      • IPC_SET 设置共享内存段属性

      • IPC_RMID 删除共享内存段

      • SHM_LOCK 锁定共享内存段页面(页面映射到的物理内存不和外存(swap)进行换入换出操作)

      • SHM_UNLOCK 解除共享内存段页面的锁定

共享内存映射和接触映射

void* shmat(int shmid,char* shmaddr,int shmflg);

  • 返回:成功返回共享内存映射到虚拟内存的空间的地址,失败返回-1
    int shmdt(char* shmaddr);

  • 返回:失败返回-1

  • 参数

    • shmid:共享内存id

    • shmaddr:映射到进程虚拟内存空间的地址,建议设置0,有操作系统分配

    • shmflg:若shmaddr设置成0,则shmflg也设置成0

      • SHM_RND

      • SHMLBA 地址为2的乘方

      • SHM_RDONLY 只读方式链接

  • errno

    • EINVAL 无效的IPC ID值或无效的地址

    • ENOMEM 没有足够的内存

    • EACCES 存取权限不够
      子进程不继承父进程创建的共享内存,大家的是共享的。子进程继承父进程映射的地址

示例
#include <stdio.h>
#include <sys/shm.h>
#include <unistd.h>
// 存放pipe,用来使进程互斥
int fp[2];
int init_pipe(void);
void close_pipe(void);
void sleep_pipe(void);
void wake_pipe(void);
int main(void)
{
        // 创建虚拟内存块
        key_t k = 888;
        int shmid;
        if((shmid = shmget(k,sizeof(int),IPC_CREAT | 0777)) == -1)
        {
                perror("shmget error");
                return 1;
        }
        // 映射共享内存,可直接在fork映射也可以在fork后映射
        /*
        int* share_p = shmat(shmid,NULL,0);
        if((int)share_p == -1)
        {
                perror("shmat error");
                return 1;
        }
        */
        // 初始化pipe
        init_pipe();
        int pid = fork();
        if(pid == -1)
        {
                perror("fork error");
                return 1;
        }
        if(pid > 0)
        {
                // 映射共享内存
                int* share_p = shmat(shmid,NULL,0);
                if((int)share_p == -1)
                {
                        perror("shmat error");
                        return 1;
                }
                *share_p = 8;
                wake_pipe();
                wait(NULL);
                // 接触共享内存映射
                shmdt(share_p);
                // 删除共享内存
                shmctl(shmid,IPC_RMID,NULL);
        }
        else if(pid == 0)
        {
                // 映射共享内存
                int* share_p = shmat(shmid,NULL,0);
                if((int)share_p == -1)
                {
                        perror("shmat error");
                        return 1;
                }
                sleep_pipe();
                printf("*share_p = %d\n",*share_p);
                // 接触共享内存映射
                shmdt(share_p);
        }
        // 关闭pipe
        close_pipe();
        return 0;
}
int init_pipe(void)
{
        return pipe(fp);
}
void close_pipe(void)
{
        close(fp[0]);
        close(fp[1]);
}
void sleep_pipe(void)
{
        char c;
        read(fp[0],(void*)&c,sizeof(c));
}
void wake_pipe(void)
{
        char c = 'D';
        write(fp[1],(void*)&c,sizeof(c));
}

信号量

  • 本质上就是共享资源的数目,用来控制对共享资源的访问

  • 用于进程间的互斥和同步

  • 每种共享资源对应一个信号量,为了便于大量共享资源的操作引入了信号集,可对所有信号量一次性操作。对信号量集中多有操作可以要求全部成功也可以部分成功

  • 二元信号量(信号灯)值为0和1

  • 对信号量作PV操作

信号量集属性
#include <sys/sem.h>
struct semid_ds{
        struct ipc_perm sem_perm;
        unsigned short sem_nsems;// of semaphores in set 信号量集中灯的数量
        time_t sem_otime; // last semop time 最后一次对信号量操作的时间
        time_t sem_ctime;// last change time 最后一次改变时间
};
创建信号量集

int semget(key_t key,int nsems,int flag);

  • 返回:成功返回信号量集id,出错返回-1

  • 参数

    • key:用户指定的信号量集键值

    • nsems:信号量集中信号量个数

    • flag:IPC_CREAT,IPC_EXCL等权限组合

  • 头文件

#include <sys/sem.h>
信号量集控制

int semctl(int semid,int semnum,int cmd,.../* union semun arg */);

  • union semun结构

// 需要自己定义
union semun{
        int val;
        struct semid_ds* buf;
        unsigned short* array;
};
  • 参数

    • semid:信号量集id

    • semnum:0表示对所有信号量操作,信号量编号从0开始

    • val:放置获取或设置信号量集中某个信号量的值

    • buf:信号量集属性指针

    • array:放置获取或设置信号量集中所有信号量的值

    • 通过cmd参数设定对信号量集要执行的操作

      • IPC_STAT 获取信号量集的熟悉 ==> buf

      • IPC_SET 设置信号量集的熟悉 ==> buf

      • IPC_RMID 删除信号量集 ==> buf

      • GETVAL 返回信号量的值 ==> val

      • SETVAL 设置semnum信号量的值 ==> val

      • GETALL 获取所有信号量的值 ==> array

      • SETALL 设置所有信号量的初始值 ==> array

  • 头文件

#include <sys/sem.h>
信号量集操作

int semop(int semid,struct sembuf* sops,size_t nsops);

  • 返回:成功返回0,出错返回-1

  • struct sembuf 结构体

struct sembuf{
        unsigned short sem_num;// member in set 对哪一个信号量进行操作
        short sem_op;// operation(negative,0,positive) 做P操作还V操作
        short sem_flg;// IPC_NOWAIT,SEM_UNDO
};
  • 参数

    • semid:信号量集id

    • sops:sembuf结构体数组指针

    • nsops:第二个参数中结构体数组的长度

    • sem_num:信号量集中信号量的编号

    • sep_op:正数为V操作,复数为P操作,0可用于对共享资源是否已完成的测试

    • sem_flg:SEM_UNDO标志,表示在进程结束时,相应的操作将被取消,如果设置了该标志,那么在进程没有释放共享资源就退出时,内核将代为释放

  • 用于信号量集中信号量的加和减操作(PV操作)

  • 可用于进程间的互斥和同步

银行取款,PV操作
#include <stdio.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/types.h>
#include <unistd.h>
typedef struct
{
        int deposit;
        int semid;
}Account;
typedef union
{
        int val;
        struct semid_ds* buf;
        unsigned short* array;
}my_semun;
int I(void);
void P(int semid);
void V(int semid);
void D(int semid);
void withdraw(Account* account,int amount);
int main(void)
{
        key_t k = 888;
        int shmid = shmget(k,sizeof(Account),IPC_CREAT | 0777);
        if(shmid == -1)
        {
                perror("shmget error");
                return 1;
        }
        // 映射
        Account* account = shmat(shmid,0,0);
        account->deposit = 888888;
        account->semid = I();
        V(account->semid);
        int pid = fork();
        if(pid == -1)
        {
                perror("fork error");
                // 解除映射
                shmdt(account);
                // 删除共享内存
                shmctl(shmid,IPC_RMID,NULL);
                // 删除信号量集
                D(account->semid);
                return 1;
        }
        else if(pid > 0)
        {
                withdraw(account,888888);
                wait(NULL);
                // 删除信号量集
                D(account->semid);
                // 解除映射
                shmdt(account);
                // 删除共享内存
                shmctl(shmid,IPC_RMID,NULL);
        }
        else if(pid == 0)
        {
                withdraw(account,888888);
                // 解除映射
                shmdt(account);
        }
        return 0;
}
void withdraw(Account* account,int amount)
{
        // 进行P操作,其他进程会阻塞
        P(account->semid);
        if(amount <= account->deposit)
        {
                account->deposit -= amount;
        }
        printf("process %d withdraw\n",(int)getpid());
        sleep(3);
        // 使其他进程继续运行
        V(account->semid);
}
int I(void)
{
        // 创建信号量集
        int semid = semget(IPC_PRIVATE,1,IPC_CREAT | 0777);
        if(semid != -1)
        {

                my_semun val;
                val.val = 0;
                // 初始化信号量集
                semctl(semid,0,SETALL,&val);
        }
        return semid;
}
void P(int semid)
{
        // 使用sembuf结构体进行P操作
        struct sembuf buf[1] = {{0,-1,SEM_UNDO}};
        semop(semid,buf,1);
}
void V(int semid)
{
        // 使用sembuf结构体进行V操作
        struct sembuf buf[1] = {{0,1,SEM_UNDO}};
        semop(semid,buf,1);
}
void D(int semid)
{
        // 删除信号量集
        semctl(semid,0,IPC_RMID,NULL);
}
读者和写者问题
#include <stdio.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/types.h>
#include <unistd.h>
typedef struct
{
        int c_semid,g_semid;
        int* data;
}Calculate;
typedef union
{
        int val;
        struct semid_ds* buf;
        unsigned short* array;
}my_semun;
int I(void);
void get_v(Calculate*);
void calculate_v(Calculate*);
void P(int semid);
void V(int semid);
void D(int semid);
int main(void)
{
        key_t k = 888;
        int shmid = shmget(k,sizeof(int),IPC_CREAT | 0777);
        if(shmid == -1)
        {
                perror("shmget error");
                return 1;
        }
        // 映射
        Calculate c;
        c.data = shmat(shmid,0,0);
        c.c_semid = I();
        c.g_semid = I();
        int pid = fork();
        if(pid == -1)
        {
                perror("fork error");
                // 解除映射
                shmdt(c.data);
                // 删除共享内存
                shmctl(shmid,IPC_RMID,NULL);
                // 删除信号量集
                D(c.c_semid);
                D(c.g_semid);
                return 1;
        }
        else if(pid > 0)
        {
                get_v(&c);
                wait(NULL);
                // 删除信号量集
                D(c.c_semid);
                D(c.g_semid);
                // 解除映射
                shmdt(c.data);
                // 删除共享内存
                shmctl(shmid,IPC_RMID,NULL);
        }
        else if(pid == 0)
        {
                calculate_v(&c);
                // 解除映射
                shmdt(c.data);
        }
        return 0;
}
void get_v(Calculate* c)
{
        for(int i = 0;i < 100;i++)
        {
                printf("get; i = %d\n",*c->data);
                *c->data = i;
                V(c->c_semid);
                P(c->g_semid);
                printf("print; i = %d\n--------\n\n",*c->data);
        }
}
void calculate_v(Calculate* c)
{
        for(int i = 0;i < 100;i++)
        {
                P(c->c_semid);
                *c->data += 8;
                printf("calculate; i = %d\n",*c->data);
                V(c->g_semid);
        }
}
int I(void)
{
        // 创建信号量集
        int semid = semget(IPC_PRIVATE,1,IPC_CREAT | 0777);
        if(semid != -1)
        {

                my_semun val;
                val.val = 0;
                // 初始化信号量集
                semctl(semid,0,SETALL,&val);
        }
        return semid;
}
void P(int semid)
{
        // 使用sembuf结构体进行P操作
        struct sembuf buf[1] = {{0,-1,SEM_UNDO}};
        semop(semid,buf,1);
}
void V(int semid)
{
        // 使用sembuf结构体进行V操作
        struct sembuf buf[1] = {{0,1,SEM_UNDO}};
        semop(semid,buf,1);
}
void D(int semid)
{
        // 删除信号量集
        semctl(semid,0,IPC_RMID,NULL);
}

linux c内存分配方式以及动态内存分配

从静态存储区分配(由系统控制)

内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,staic变量

在栈上分配(由系统控制)

在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。效率很高,凡事分配的内存容量有限

在堆上分配,亦称动态内存分配(由自己控制)

程序在运行时用malloc等函数申请任意多少的内存,程序员自己负责在何时用free释放内存。动态内存的生命周期有我们自己决定,使用非常灵活,但问题也最多

malloc函数

void *malloc(size_t size);

  • 使用:需要包含malloc.h头文件

  • 功能:允许从空闲内存池中分配连续内存但不初始化

  • 参数:size参数是一个所需字节

  • 返回:若分配成功则返回一个指向该内存块的指针,在使用时可根据需要做强制类型转换,否则返回NULL指针

calloc函数

void *calloc(size_t num_elements,size_t element_size);

  • 使用:需要包含malloc.h头文件

  • 功能:功能与malloc函数相同,但作初始化

  • 参数:num_elements是所需元素的数量,element_size是每个元素的字节

  • 返回:与malloc相同

realloc含糊

void *realloc(void *ptr,size_t new_size)

  • 使用:需要包含malloc.h头文件

  • 功能:在指针ptr指向的内存基础上扩大或缩小内存

  • 参数:ptr是指向先前通过malloccallocrealloc函数后分配的内存块的指针,new_size是内存块的新尺寸,可能大于或小于原有内存尺寸

  • 返回:若能调整内存大小则返回指向调整后内存的指针,否则返回NULL

realloc函数的使用注意点
  • 当扩展内存时,不会对添加的内存块的字节进行初始化

  • 弱不能调整内存则返回NULL,但原有内存中的数据不会发生改变

  • 若第一个参数为NULL那么功能等同于malloc函数,若第二个参数为0,那么会释放调用内存块。

  • 在缩小或扩大内存时一般不会对其进行移动,若无法扩大内存块(内存块后面的字节已经用于其他木的),那么可能会在别处分配心的内存块,然后把旧块的数据复制到新块中,并将旧块删除释放内存

free函数

void free(void *ptr);

  • 功能:释放由指针ptr指向的动态内存分配的内存块

free函数注意点
  • 如果ptrNULL指针,那么freeptr无论操作多少次都不会出问题

  • 如果ptr不是NULL指针,那么freeptr连续操作两次就会导致程序停止运行

示例

// stdlib会包含malloc
#include <stdlib.h>
#include <stdio.h>
void print_arr(int *p,int length);
int main(void)
{
        size_t length = 0;
        scanf("%d",&length);
        if(length > 0)
        {
                // 使用malloc分配内存
                //int *p = (int*)malloc(length);
                // 使用calloc分配内存
                int *p = (int*)calloc(sizeof(int),length);
                p[0] = 8;
                print_arr(p,length);
                // 使用 realloc修改申请的内存空间的大小
                p = realloc(p,length + 2);
                print_arr(p,length + 2);
                free(p);
                // 注意虽然释放了申请的内存,但是这个注意要清空下指针
                p = NULL;
        }
        return 0;
}
void print_arr(int *p,int length)
{
        for(int i = 0;i < length;i++)
        {
                printf("p[%d] = %d;\n",i,p[i]);
        }
}

超级猜图swift

代码太多,贴几个重点吧

通过plist初始化(kvc),可以很方便的对类的属性进行赋值
//
//  Question.swift
//  超级猜图
//
//  Created by admin on 15/12/30.
//  Copyright © 2015年 jin. All rights reserved.
//

import UIKit

class Question: NSObject {
    var answer:NSString!
    var icon:NSString!
    var title:NSString!
    var options:NSArray!
    //加载文件,初始化对象数组
    class func loadQuestionOfFile()->[Question]
    {
        let datas:NSArray! = NSArray(contentsOfFile: NSBundle.mainBundle().pathForResource("questions", ofType: "plist")!)
        var temp:[Question] = []
        for data in datas
        {
            temp.append(Question.instance(data as! NSDictionary))
        }
        return temp
    }
    //传入字典,初始化对象
    class func instance(questionData:NSDictionary)->Question
    {
//        var temp = Question(answer:questionData["answer"] as! NSString,title:questionData["title"] as! NSString,icon:questionData["icon"] as! NSString,option:questionData["option"] as! NSArray)
//        self.init(answer:questionData["answer"] as! NSString,title:questionData["title"] as! NSString,icon:questionData["icon"] as! NSString,option:questionData["options"] as! NSArray)
        let temp = Question()
        //直接使用字典的键值对队完成对对象属性的肤赋值
        temp.setValuesForKeysWithDictionary(questionData as! [String : AnyObject])
        return temp
    }
}

kvc其它用法
//取出对象数组中对象的icon值
print((self.questions as NSArray).valueForKeyPath("icon")!)
//取出对象数组中对象的book值(是一个类)的name属性,这里执行会报错,oc使用点分割swift呢?
print(self.questions[0].valueForKeyPath("book.name"))
隐藏程序界面状态栏
override func prefersStatusBarHidden() -> Bool {
     return true
}
隐藏启动界面状态栏

General>Deployment Info>Hide status bar勾选就好了

修改状态栏 style
override func preferredStatusBarStyle() -> UIStatusBarStyle {
    return UIStatusBarStyle.LightContent//居中
}
删除子控件
self.answerButtonView.subviews.forEach({$0.removeFromSuperview()})
弹出框
var alert = UIAlertView(title: "提示", message: "后面没有题了", delegate: nil, cancelButtonTitle: "取消", otherButtonTitles: "确认", "测试A","测试B")
控件放到最顶层
self.view.bringSubviewToFront(self.imageView)