姓名:王顺其; 学号:20181214438; 学院:广州研究院(空间科学与技术学院)
【嵌牛导读】
对于一个多线程的程序线程之间的通信是非常方便的,但是我们在一些特定场景不能使用或者说使用多线程不能达到最佳效果,这时就要使用到进程间的通信,而由于进程间的资源是相互独立的,所以我们需要一些特殊的方法实现进程间的通信,下面主要分成三大类来简单阐述一下进程间是如何实现通信的。
【嵌牛鼻子】
- 1 线程管道
- 2 进程间通信
- 3 网络套接字 socket
【嵌牛提问】
我们如何在Linux环境下实现进程通信?这些机制各有什么优缺点呢?
【嵌牛正文】
1线程管道
筛选质数的程序,我们可以考虑从普通的单线程到多线程,再到多线程的池类方法,再到多线程+通知法,一步一步对其进行优化,可以达到了很好的效果。但是我们要思考最后我们实现的方案还是由一个主线程每次下发一个任务,由下面的子线程来抢任务,在任务量和线程数较少的时候显然这种方案是可行的,但是一旦任务量过多且线程数过多,其效率就会变得非常低。
对于以上问题我们考虑一次下发的任务量不再为1,而是一个array,由上下游约定存取方式(可以考虑队列)(管道)
例程:
mypipe.h
#ifndef MYPIPE_H__
#define MYPIPE_H__
#define PIPESIZE 1024
#define MYPIPE_READ 0x00000001UL
#define MYPIPE_WRITE 0x0000002UL
typedef void mypipe_t;
mypipe_t *mypipe_init(void);
int mypipe_register(mypipe_t *, int op);
int mypipe_unregister(mypipe_t *, int op);
int mypipe_read(mypipe_t *, void *buf, int size);
int mypipe_write(mypipe_t *, const void *buf, int size);
int mypipe_destroy(mypipe_t *);
#endif
mypipe.c
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include "mypipe.h"
struct mypipe_st{
int head;
int tail;
char data[PIPESIZE];
int datasize;
int count_reader;
int count_writer;
pthread_mutex_t mut;
pthread_cond_t cond;
};
mypipe_t *mypipe_init(void){
struct mypipe_st *me;
me = malloc(sizeof(*me));
if (me == NULL){
return NULL;
}
me->head = 0;
me->tail = 0;
me->datasize = 0;
me->count_reader = 0;
me->count_writer = 0;
pthread_mutex_init(&me->mut, NULL);
pthread_cond_init(&me->cond, NULL);
return me;
}
int mypipe_register(mypipe_t *ptr, int op){
struct mypipe_st *me = ptr;
pthread_mutex_lock(&me->mut);
if(op & MYPIPE_READ){
me->count_reader++;
}
if(op & MYPIPE_WRITE){
me->count_writer++;
}
pthread_cond_broadcast(&me->cond);
while(me->count_reader <= 0 || me->count_writer <= 0){
pthread_cond_wait(&me->cond, &me->mut);
}
pthread_mutex_unlock(&me->mut);
return 0;
}
int mypipe_unregister(mypipe_t *ptr, int op){
struct mypipe_st *me = ptr;
pthread_mutex_lock(&me->mut);
if(op & MYPIPE_READ){
me->count_reader--;
}
if(op & MYPIPE_WRITE){
me->count_writer--;
}
pthread_cond_broadcast(&me->cond);
pthread_mutex_unlock(&me->mut);
return 0;
}
static int mypipe_readbyte_unlocked(struct mypipe_st *me, char *datap){
if(me->datasize <= 0 )
return -1;
*datap = me->data[me->head];
me->head = next(me->head);
me->datasize --;
return 0;
}
int mypipe_read(mypipe_t *ptr, void *buf, int size){
struct mypipe_st *me = ptr;
int i;
pthread_mutex_lock(&me->mut);
while(me->datasize <= 0 && me->count_writer > 0){
pthread_cond_wait(&me->cond, &me->mut);
}
if(me->datasize <=0 && me->count_writer <= 0){
pthread_mutex_unlock(&me->mut);
return 0;
}
for(i = 0; i < size; i++){
if (mypipe_readbyte_unlocked(me, buf+i) != 0){
break;
}
}
pthread_cond_broadcast(&me->cond);
pthread_mutex_unlock(&me->mut);
}
int mypipe_write(mypipe_t *ptr, const void *buf, int size){
//.....
}
int mypipe_destroy(mypipe_t *ptr){
struct mypipe_st *me = ptr;
pthread_mutex_destroy(&me->mut);
pthread_cond_destroy(&me->cond);
free(ptr);
return 0;
}
以上程序的write部分未完成,但是已经具备了管道(多线程)的结构特点,管道至少要同时纯在一对读者写者才能构成管道可以工作。
对于这部分存在权限管理的问题:有可能注册的读写行为、实际操作的读写行为以及注销的读写行为可能存在不一致的问题。解决方案是通过对该方案不在过于底层的部分进行权限校验,而是进行二次封装时设置权限位图,进行权限的校验。
2 进程间通信
1 管道
2 XSI -> SysV
3 网络套接字 socket
2.1 管道
内核提供,单工,自同步机制(永远迁就速度慢的一方)
2.1.1 匿名管道
由内核维护创建,没有文件名,在文件系统上不显示,只有有亲缘关系的进程才能使用。
函数 pipe
NAME
pipe, pipe2 - create pipe
SYNOPSIS
#include <unistd.h>
int pipe(int pipefd[2]);//0端为读端 1端为写端
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include <fcntl.h> /* Obtain O_* constant definitions */
#include <unistd.h>
int pipe2(int pipefd[2], int flags);
DESCRIPTION
pipe() creates a pipe, a unidirectional data channel that can be
used for interprocess communication. The array pipefd is used to
return two file descriptors referring to the ends of the pipe.
pipefd[0] refers to the read end of the pipe. pipefd[1] refers to the
write end of the pipe. Data written to the write end of the pipe is
buffered by the kernel until it is read from the read end of the pipe.
For further details, see pipe(7).
简单实例:父进程写管道 子进程读管道
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <wait.h>
#define BUFSIZE 1024
int main(){
int pd[2];
pid_t pid;
char buf[BUFSIZE];
int len;
if(pipe(pd) < 0){
perror("pipe():");
exit(1);
}
pid = fork();
if(pid < 0){
perror("fork():");
exit(1);
}
if(pid == 0){ //子进程读管道
close(pd[1]);
len = read(pd[0], buf, BUFSIZE);
write(1, buf, len);
close(pd[0]);
exit(0);
}
else{ //父进程写管道
close(pd[0]);
write(pd[1], "Hello!\n", 7);
close(pd[1]);
wait(NULL);
exit(0);
}
}
2.1.2 命名管道
函数:mkfifo()
NAME
mkfifo, mkfifoat - make a FIFO special file (a named pipe)
SYNOPSIS
#include <sys/types.h>
#include <sys/stat.h>
/*
pathname 管道文件名字
mode 权限
*/
int mkfifo(const char *pathname, mode_t mode);
#include <fcntl.h> /* Definition of AT_* constants */
#include <sys/stat.h>
int mkfifoat(int dirfd, const char *pathname, mode_t mode);
2.2 XSI
IPC -> Inter-Process Communication
主动端:先发包的一端
被动端:先收包的一端(先运行)
key :获取key值 ftok()
Message Queues 消息队列
Semaphore Arrays 信号量数组
Shared Memory Segments 共享内存
- ftok
NAME
ftok - convert a pathname and a project identifier to a System V IPC key
SYNOPSIS
#include <sys/types.h>
#include <sys/ipc.h>
//指定一个哈希值 指定一个哈希掺杂 获得一个key
key_t ftok(const char *pathname, int proj_id);
对于以上三种方式都有一下操作
xxxget 创建
xxxop 操作
xxxctl 控制
xxx -> msg sem shm
2.2.1 消息队列
- msgget msgop msgctl
NAME
msgget - get a System V message queue identifier
msgrcv, msgsnd - System V message queue operations
msgctl - System V message control operations
SYNOPSIS
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,
int msgflg);
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
- 消息队列使用实例
协议文件 proto.h
#ifndef PROTO_H__
#define PROTO_H__
#define NAMESIZE 64
#define KEYPATH "/etc/services"
#define KEYPROJ 'g'
struct msg_st
{
long mtype;
char name[NAMESIZE];
int math;
int chinese;
};
#endif
发送方 snder.h
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
#include "proto.h"
int main(){
key_t key;
int msgid;
struct msg_st sbuf;
key = ftok(KEYPATH, KEYPROJ);
if(key < 0){
perror("ftok:");
exit(1);
}
msgid = msgget(key, 0);
if(msgid < 0){
perror("msgget:");
exit(1);
}
sbuf.mtype = 1;
strcpy(sbuf.name, "Alan");
sbuf.math = rand()%100;
sbuf.chinese = rand()%100;
if(msgsnd(msgid, &sbuf, sizeof(sbuf)-sizeof(long), 0) < 0){
perror("msgrcv:");
exit(1);
}
puts("OK!");
exit(0);
}
接收方 rcver.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include "proto.h"
int main(){
key_t key;
int msgid;
struct msg_st rbuf;
key = ftok(KEYPATH, KEYPROJ);
if(key < 0){
perror("ftok:");
exit(1);
}
msgid = msgget(key, IPC_CREAT|0600);
if(msgid < 0){
perror("msgget:");
exit(1);
}
while(1){
if(msgrcv(msgid, &rbuf, sizeof(rbuf)-sizeof(long), 0, 0) < 0){
perror("msgrcv:");
exit(1);
}
printf("NAME = %s\n", rbuf.name);
printf("MATH = %d\n", rbuf.math);
printf("CHINESE = %d\n", rbuf.chinese);
printf("\n");
}
msgctl(msgid, IPC_RMID, NULL);
exit(0);
}
先运行 接收方循环等待消息队列,再运行发送方向队列中发送消息。
- 数据结构的封装优化
proto1.h 比较标准的处理方法
#ifndef PROTO1_H__
#define PROTO1_H__
#define KEYPATH "/etc/services"
#define KEYPROJ 'a'
#define PATHMAX 1024
#define DATAMAX 1024
enum{
MSG_PATH = 1,
MSG_DATA,
MSG_EOT
};
typedef struct msg_path_st {
long mtype; /* must be MSG_PATH */
char path[PATHMAX]; /* ASCIIZ 带尾0的串 */
}msg_path_t;
typedef struct msg_data_st {
long mtype; /* must be MSG_DATA */
char data[DATAMAX];
int datalen;
}msg_data_t;
typedef struct msg_eot_st {
long mtype; /* must be MSG_EOT */
}msg_eot_t;
//这里的三个成员共享一段空间,
//直接读mtype就相当于读了下边两个结构体中的mtype,
//从而实现对包类型的判断
union msg_s2c_un {
long mtype;
msg_data_t datamsg;
msg_eot_st eotmsg;
};
#endif
proto2.h 比较不标准(简单)的处理方法
#ifndef PROTO1_H__
#define PROTO1_H__
#define KEYPATH "/etc/services"
#define KEYPROJ 'a'
#define PATHMAX 1024
#define DATAMAX 1024
enum{
MSG_PATH = 1,
MSG_DATA,
MSG_EOT
};
typedef struct msg_path_st {
long mtype; /* must be MSG_PATH */
char path[PATHMAX]; /* ASCIIZ 带尾0的串 */
}msg_path_t;
typedef struct msg_s2c_st {
long mtype; //直接在这里注明是 data包还是eot包
int datalen;
/** 或者使用datalen说明包类型
* datalen > 0 data 包;
* == 0 eot 包;
*/
char data[DATAMAX];
}msg_s2c_t;
#endif
2.2.3 信号量数组
- semget semop semctl
NAME
semget - get a System V semaphore set identifier
semop, semtimedop - System V semaphore operations
semctl - System V semaphore control operations
SYNOPSIS
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
/*
由于如果是父进程创建sem id 的话所有子进程无需二次创建
便可获得sem id 这样的话用户就不必关心key值
所以这里有一个私有秘钥参数 IPC_PRIVATE 供用户使用
//nsems 信号量中有几个元素
//semflg 选项或上创建权限
*/
int semget(key_t key, int nsems, int semflg);
int semop(int semid, struct sembuf *sops, size_t nsops);
int semtimedop(int semid, struct sembuf *sops, size_t nsops,
const struct timespec *timeout);
int semctl(int semid, int semnum, int cmd, ...);
2.2.4 共享内存
- shmget shmop shmctl
NAME
shmget - allocates a System V shared memory segment
shmat, shmdt - System V shared memory operations
shmctl - System V shared memory control
SYNOPSIS
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
void *shmat(int shmid, const void *shmaddr, int shmflg);
int shmdt(const void *shmaddr);
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
- 简单实例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <wait.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define BUFSIZE 1024
int main(){
int shmid;
pid_t pid;
char *ptr;
shmid = shmget(IPC_PRIVATE, BUFSIZE, 0600);
if(shmid < 0){
perror("shmget");
exit(1);
}
pid = fork();
if(pid < 0){
perror("fork");
exit(1);
}
if( pid == 0){
ptr = shmat(shmid, NULL, 0);//内存映射
if(ptr == (void *)-1){
perror("shmget");
exit(1);
}
strcpy(ptr, "hello");
shmdt(ptr);//解除内存映射
exit(0);
}
else{
wait(NULL);
ptr = shmat(shmid, NULL, 0);//内存映射
if(ptr == (void *)-1){
perror("shmget");
exit(1);
}
puts(ptr);
shmdt(ptr);//解除内存映射
shmctl(shmid, IPC_RMID, NULL);//销毁实例
exit(0);
}
}
3 网络套接字 socket
讨论:
报式套接字:
流式套接字:
3.1 跨主机的传输问题
3.1.1 字节序问题
大端存储:低地址处放高字节
小端存储:低地址处放低字节 x86架构
无论是IO还是网络传输都是低地址数据先发出,为了解决这个问题,网络传输中不再区分存储方式,而是区分是主机字节序还是网络字节序。
主机字节序:host
网络字节序:network
to_: htons htonl ntohs ntohl
3.1.2 字节对其
例如:
struct {
int i; //32/64bit-4byte
float f; //32/64bit-4byte
char ch; //32/64bit-1byte
}
理论上上述结构体所占空间应该为 9 byte ,但是编译器在编译过程中会将该结构体进行字节对齐,其目的是为了节省取指周期。例如32位环境下会以4个字节为单位进行对齐,上述结构体就会占 12byte 。
struct {
int i; //32-4byte
float f; //32-4byte
char ch; //32-1byte
} //32bit - toal 12byte
struct {
int i; //32-4byte
char chx; //32-1byte
float f; //32-4byte
char ch; //32-1byte
} //32bit - toal 16byte
struct {
int i; //32-4byte
float f; //32-4byte
char ch; //32-1byte
char chx; //32-1byte
} //32bit - toal 12byte
对于对齐问题的解决办法就是不对齐,在定义的时候加入宏,告知编译器不对齐。
3.1.3 类型长度问题
标准 C 未对数据类型大小进行明确定义,解决办法 int32_t uint32_t int8_t 等方法。
3.2 socket
socket 是一个中间层,一个用来支持不同网络协议和不同传输方式的实现。返回值是一个抽象出来的文件描述符。
NAME
socket - create an endpoint for communication
SYNOPSIS
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
/*
domain 域 - 协议族 地址族
type 上层如何实现
protocol 协议
我要使用 domain 协议族中的 protocol 协议用 type 的方式完成传输。
*/
int socket(int domain, int type, int protocol);
domain //-------
Name Purpose Man page
AF_UNIX, AF_LOCAL Local communication unix(7)
AF_INET IPv4 Internet protocols ip(7)
AF_INET6 IPv6 Internet protocols ipv6(7)
AF_IPX IPX - Novell protocols
AF_NETLINK Kernel user interface device netlink(7)
AF_X25 ITU-T X.25 / ISO-8208 protocol x25(7)
AF_AX25 Amateur radio AX.25 protocol
AF_ATMPVC Access to raw ATM PVCs
AF_APPLETALK AppleTalk ddp(7)
AF_PACKET Low level packet interface packet(7)
AF_ALG Interface to kernel crypto API
type //------------
//流式 —— 有序的 可靠的 双工的 基于链接的 字节流的
SOCK_STREAM Provides sequenced, reliable, two-way,
connection-based byte streams. An out-of-band
data transmission mechanism may be supported.
//报式
SOCK_DGRAM Supports datagrams (connectionless, unreliable
messages of a fixed maximum length).
//有序分组式 ———— 安全可靠的报式传输
SOCK_SEQPACKET Provides a sequenced, reliable, two-way
connection-based data transmission path for
datagrams of fixed maximum length; a consumer
is required to read an entire packet with
each input system call.
//网络协议层的访问
SOCK_RAW Provides raw network protocol access.
//数据层的访问
SOCK_RDM Provides a reliable datagram layer that does
not guarantee ordering.
//不要随便用这个
SOCK_PACKET Obsolete and should not be used in new
programs; see packet(7).
3.2.1 报式套接字
- bind inet_pton
NAME
bind - bind a name to a socket
SYNOPSIS
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
NAME
inet_pton - convert IPv4 and IPv6 addresses from text to binary form
SYNOPSIS
#include <arpa/inet.h>
//将一个ip地址转换成二进制的数
/*
af 协议族 AF_INET / AF_INET6
src ip地址
dst 结果保存位置
*/
int inet_pton(int af, const char *src, void *dst);
- recv, recvfrom, recvmsg
NAME
recv, recvfrom, recvmsg - receive a message from a socket
SYNOPSIS
#include <sys/types.h>
#include <sys/socket.h>
//流式接收
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
//报式接收
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr,
socklen_t *addrlen);
//
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
NAME
send, sendto, sendmsg - send a message on a socket
SYNOPSIS
#include <sys/types.h>
#include <sys/socket.h>
//流式发送
ssize_t send(int sockfd, const void *buf, size_t len,
int flags);
//报式发送
ssize_t sendto(int sockfd, const void *buf, size_t len,
int flags, const struct sockaddr *dest_addr,
socklen_t addrlen);
ssize_t sendmsg(int sockfd, const struct msghdr *msg,
int flags);
- 报式socket实例
proto.h
#ifndef PROTO_H__
#define PROTO_H__
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>
#define RCVPORT "1989" //port num
#define NAMESIZE 11 //name size
//#pragma pack(1)
//struct of message
struct msg_st{
uint8_t name[NAMESIZE]; //name type
uint32_t math; //math score
uint32_t chinese; //chinese score
}__attribute__((packed)); //set no byte alignment
#endif
rcver.c 接收端 被动端
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>
#include "proto.h"
int main(){
int sd; //文件描述符
struct sockaddr_in laddr, raddr; //远端 近端 ip
struct msg_st rbuf; //接收缓冲区
socklen_t raddr_len; //近端ip结构体大小
char ipstr[64]; //ip字符串
//socket链接
sd = socket(AF_INET, SOCK_DGRAM, 0 /* IPPROTO_UDP */);
if(sd < 0){
exit(1);
}
//地址格式设置
laddr.sin_family = AF_INET;
laddr.sin_port = htons(atoi(RCVPORT));
//参数转换 0.0.0.0 表示适配自己当前的ip地址
inet_pton(AF_INET,"0.0.0.0",&laddr.sin_addr.s_addr);
//绑定地址和端口
if(bind(sd, (void *)&laddr, sizeof(laddr)) < 0){
perror("bind");
exit(1);
}
/* !!! */
raddr_len = sizeof(raddr);//不初始化会导致第一次回填地址失败
while(1){ //循环等待接收并打印接收的消息
recvfrom(sd, &rbuf, sizeof(rbuf), 0, \
(void *)&raddr, &raddr_len);
inet_ntop(AF_INET, &raddr.sin_addr, ipstr, 64);
printf("---MESSAGE FROM %s:%d---\n", ipstr,\
ntohs(raddr.sin_port));
printf("NAME = %s\n", rbuf.name);
printf("MATH = %d\n", ntohl(rbuf.math));
printf("CHINESE = %d\n", ntohl(rbuf.chinese));
}
close(sd);
return 0;
}
snder.c 发送端 主动端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>
#include "proto.h"
int main(int argc, char *argv[]){
if(argc < 2){
fprintf(stderr, "Usage....\n");
exit(1);
}
int sd;
struct msg_st sbuf;
struct sockaddr_in raddr;
sd = socket(AF_INET, SOCK_DGRAM, 0);
if(sd < 0){
exit(1);
}
// bind();
strcpy(sbuf.name, "Alan");
sbuf.math = htonl(rand() % 100);
sbuf.chinese = htonl(rand() % 100);
raddr.sin_family = AF_INET;
raddr.sin_port = htons(atoi(RCVPORT));
inet_pton(AF_INET, argv[1], &raddr.sin_addr.s_addr);
if(sendto(sd, &sbuf, sizeof(sbuf), 0, (void *)&raddr,\
sizeof(raddr)) < 0){
perror("sendto");
exit(1);
}
puts("OK!");
close(sd);
return 0;
}
- 变长的报式socket
proto,h
#ifndef PROTO_H__
#define PROTO_H__
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>
#define RCVPORT "1989" //port num
#define NAMEMAX (512-8-8)//UDP 的推荐长度
//#pragma pack(1)
//struct of message
struct msg_st{
uint32_t math; //math score
uint32_t chinese; //chinese score
uint8_t name[1]; //占位符
}__attribute__((packed)); //set no byte alignment
#endif
snder.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>
#include "proto.h"
int main(int argc, char *argv[]){
if(argc < 3){
fprintf(stderr, "Usage....\n");
exit(1);
}
int sd, size;
struct msg_st *sbufp;
struct sockaddr_in raddr;
if(strlen(argv[2]) > NAMEMAX){
fprintf(stderr, "Name is too long.\n");
exit(1);
}
sd = socket(AF_INET, SOCK_DGRAM, 0);
if(sd < 0){
exit(1);
}
// bind();
size = sizeof(struct msg_st) + strlen(argv[2]);
sbufp = malloc(size);
if(sbufp == NULL){
perror("malloc");
exit(1);
}
strcpy(sbufp->name, argv[2]);
sbufp->math = htonl(rand() % 100);
sbufp->chinese = htonl(rand() % 100);
raddr.sin_family = AF_INET;
raddr.sin_port = htons(atoi(RCVPORT));
inet_pton(AF_INET, argv[1], &raddr.sin_addr.s_addr);
if(sendto(sd, sbufp, size, 0, (void *)&raddr,\
sizeof(raddr)) < 0){
perror("sendto");
exit(1);
}
puts("OK!");
close(sd);
return 0;
}
rcver.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>
#include "proto.h"
int main(){
int sd, size; //文件描述符
struct sockaddr_in laddr, raddr; //远端 近端 ip
struct msg_st *rbufp; //接收缓冲区
socklen_t raddr_len; //近端ip结构体大小
char ipstr[64]; //ip字符串
size = sizeof(struct msg_st) + NAMEMAX - 1;
rbufp = malloc(size);
if(rbufp == NULL){
perror("malloc");
exit(1);
}
//socket链接
sd = socket(AF_INET, SOCK_DGRAM, 0 /* IPPROTO_UDP */);
if(sd < 0){
exit(1);
}
//地址格式设置
laddr.sin_family = AF_INET;
laddr.sin_port = htons(atoi(RCVPORT));
//参数转换 0.0.0.0 表示适配自己当前的ip地址
inet_pton(AF_INET,"0.0.0.0",&laddr.sin_addr.s_addr);
//绑定地址和端口
if(bind(sd, (void *)&laddr, sizeof(laddr)) < 0){
perror("bind");
exit(1);
}
/* !!! */
raddr_len = sizeof(raddr);//不初始化会导致第一次回填地址失败
while(1){ //循环等待接收并打印接收的消息
recvfrom(sd, rbufp, size, 0, \
(void *)&raddr, &raddr_len);
inet_ntop(AF_INET, &raddr.sin_addr, ipstr, 64);
printf("---MESSAGE FROM %s:%d---\n", ipstr,\
ntohs(raddr.sin_port));
printf("NAME = %s\n", rbufp->name);
printf("MATH = %d\n", ntohl(rbufp->math));
printf("CHINESE = %d\n", ntohl(rbufp->chinese));
}
close(sd);
return 0;
}
- 广播
多点通讯:
广播(全网广播、子网广播) 多播/组播
全网广播默认是被禁止的,
setsockopt
getsockopt
NAME
getsockopt, setsockopt - get and set options on sockets
SYNOPSIS
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int getsockopt(int sockfd, int level, int optname,
void *optval, socklen_t *optlen);
//参数
/*
sockfd
level
optname
optval
optlen
*/
int setsockopt(int sockfd, int level, int optname,
const void *optval, socklen_t optlen);
设置广播允许的代码段
//属性设置
int val = 1;
if(setsockopt(sd, SOL_SOCKET, SO_BROADCAST, &val, sizeof(val)) < 0){
perror("setopt");
exit(1);
}
inet_pton(AF_INET, "255.255.255.255", &raddr.sin_addr.s_addr);
- 多播
if_nametoindex
NAME
if_nametoindex, if_indextoname - mappings between network interface names and indexes
SYNOPSIS
#include <net/if.h>
unsigned int if_nametoindex(const char *ifname);
char *if_indextoname(unsigned int ifindex, char *ifname);
设置多允许的代码段 创建/加入多播组
// 加入多播 组属性设置
struct ip_mreqn mreq;
inet_pton(AF_INET, MTGROUP, &mreq.imr_multiaddr);
inet_pton(AF_INET, "0.0.0.0", &mreq.imr_address);
mreq.imr_ifindex = if_nametoindex("eth0");
if(setsockopt(sd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0){
perror("setopt");
exit(1);
}
//创建多播 属性设置
struct ip_mreqn mreq;
inet_pton(AF_INET, MTGROUP, &mreq.imr_multiaddr);
inet_pton(AF_INET, "0.0.0.0", &mreq.imr_address);
mreq.imr_ifindex = if_nametoindex("eth0");
if(setsockopt(sd, IPPROTO_IP, IP_MULTICAST_IF, &mreq, sizeof(mreq)) < 0){
perror("setopt");
exit(1);
}
多播特殊的地址 224.0.0.1 所有支持多播的节点都在组内且无法离开。
3.2.2 传输协议分析
- UDP 传输分析
UDP 会出现丢包,使用抓包器会发现有 TTL(time to leave) ,路由距离限制(Linux一般 64 ,windows一般 128)。很少是因为 TTL 耗尽,大多数是因为阻塞造成丢包。
解决阻塞的方法 流控 。
- ==停等式流控==
停等式流控并没有减低丢包率,1)与可能DATA没丢,回来的ACK丢了,不但不会降低丢包率还会增加丢包率。2)是牺牲了丢包率换取了接收正确的数据。
先设置一个 RTT 每次才收到 ACK 后跟 RTT 做平均。RTT 为等待时间。DATA 包需要添加编号,确定收到包的顺序,同样ACK也需要编号。
- TCP 传输分析
发一个 RTT 时间的 DATA 包,直到收到 ACK 后在继续发包。
滑动窗口:第一次发一个 RTT 的包,此后每收到一个 ACK 发两个(或几个),直到丢包了,从丢包妈的位置把窗口砍掉一一半在重新发包。尽最大可能抢占沿途的路由资源。但是接收端不一定是按顺序收包,返回的 ACK 的编号存在问题,无法确定该会那个编号。解决方法:两组编号,三次握手,将报式传输转变成流式传输(TCP)。
三次握手(TCP 连接的建立)
1)C to S: SYN = N-1; 告诉 S 端,我的编号从N开始。
2)S to C: ACK = N, SYN = M; 告诉 C 端,我知道你从N开始,你得知道我从M开始。
3)C to S: ACK = M; 好了我知道你从M开始了。
当完成1)2)步之后的状态叫做 “半连接状态” ,在 S 端会有一个 半连接池,记录半连接的连接,当发生第三次时会从半连接池中寻找是否存在已经两次握手的连接,如果有就正常建立数据流的传输连接。
衍生出的攻击形式:半连接洪水,非常下流。。。。目的占满半连接池,永远发第一次握手,收第二次握手,永远不发第三次握手。防范手段,去掉半连接池,改用对端的 IP 和端口加上我端的 IP 加端口再加上协议信息或上一个参杂串(salt)做哈希,得的内容叫做 cookie,第二次握手的时候 S 端把 cookie 放包里发给 C 端,C 端第三次握手的时候需要带着 cookie 来,S 端会对 cookie 进行校验。这里的 salt 是由内核产生每秒变一次,校验时只会 用最近两秒内的 salt 进行校验。
3.2.3 流式套接字
listen
NAME
listen - listen for connections on a socket
SYNOPSIS
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int listen(int sockfd, int backlog);
DESCRIPTION
listen() marks the socket referred to by sockfd as a passive
socket, that is, as a socket that will be used to accept incoming
connection requests using accept(2).
The sockfd argument is a file descriptor that refers to a socket
of type SOCK_STREAM or SOCK_SEQPACKET.
The backlog argument defines the maximum length to which the
queue of pending connections for sockfd may grow.
If a connection request arrives when the queue is full, the
client may receive an error with an indication of ECONNREFUSED or,
if the underlying protocol supports retransmission, the request may
be ignored so that a later reattempt at connection succeeds.
accept
NAME
accept, accept4 - accept a connection on a socket
SYNOPSIS
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr,
socklen_t *addrlen);
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include <sys/socket.h>
int accept4(int sockfd, struct sockaddr *addr,
socklen_t *addrlen, int flags);
connect
NAME
connect - initiate a connection on a socket
SYNOPSIS
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
- 简单实现
proto.h
#ifndef PROTO_H__
#define PROTO_H__
#define SERVERPOT "1989"
#define FMT_STAMP "%lld\r\n"
#endif
server.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>
#include "proto.h"
#define IPSTRSIZE 40
#define BUFSIZE 1024
static void server_job(int sd){
char buf[BUFSIZE];
int len;
len = sprintf(buf, FMT_STAMP, (long long)time(NULL));
if(send(sd, buf, len, 0) < 0){
perror("send()");
exit(1);
}
}
int main(){
int sd, newsd;
struct sockaddr_in laddr, raddr;
socklen_t raddr_len;
char ipstr[IPSTRSIZE];
sd = socket(AF_INET, SOCK_STREAM, 0 /* IPPROTO_TCP IPPROTO_SCTP*/);
if(sd < 0){
perror("socket()");
exit(1);
}
int val = 1;
if(setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) < 0){
perror("setsockopt()");
exit(1);
}
laddr.sin_family = AF_INET;
laddr.sin_port = htons(atoi(SERVERPOT));
inet_pton(AF_INET, "127.0.0.1", &laddr.sin_addr);
if(bind(sd, (void *)&laddr, sizeof(laddr)) < 0){
perror("bind()");
exit(1);
}
if(listen(sd, 200) < 0){
perror("listen()");
exit(1);
}
raddr_len = sizeof(raddr);
while(1){
newsd = accept(sd, (void *)&raddr, &raddr_len);
if(newsd < 0){
perror("accept()");
exit(1);
}
inet_ntop(AF_INET, &raddr.sin_addr, ipstr, IPSTRSIZE);
printf("Client:%s:%d\n", ipstr, ntohs(raddr.sin_port));
server_job(newsd);
close(newsd);
}
close(sd);
}
client.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>
#include "proto.h"
#define IPSTRSIZE 40
#define BUFSIZE 1024
int main(int argc, char* argv[]){
if(argc < 2){
printf("Usage...\n");
exit(1);
}
int sd, newsd;
FILE* fp;
long long stamp;
struct sockaddr_in laddr, raddr;
socklen_t raddr_len;
char ipstr[IPSTRSIZE];
sd = socket(AF_INET, SOCK_STREAM, 0 /* IPPROTO_TCP IPPROTO_SCTP*/);
if(sd < 0){
perror("socket()");
exit(1);
}
raddr.sin_family = AF_INET;
raddr.sin_port = htons(atoi(SERVERPOT));
inet_pton(AF_INET, argv[1], &raddr.sin_addr);
if(connect(sd, (void *)&raddr, sizeof(raddr)) < 0){
perror("connect()");
exit(1);
}
//recv();
//close();
fp = fdopen(sd, "r");
if(fp == NULL){
perror("fdopen()");
exit(1);
}
//fread();
if(fscanf(fp, FMT_STAMP, &stamp) < 1){
fprintf(stderr, "Bad format!\n");
}
else
{
fprintf(stdout, "stamp = %lld\n", stamp);
}
fclose(fp);
exit(0);
}
并发版 server.c (多进程)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>
#include "proto.h"
#define IPSTRSIZE 40
#define BUFSIZE 1024
static void server_job(int sd){
char buf[BUFSIZE];
int len;
len = sprintf(buf, FMT_STAMP, (long long)time(NULL));
if(send(sd, buf, len, 0) < 0){
perror("send()");
exit(1);
}
}
int main(){
pid_t pid;
int sd, newsd;
struct sockaddr_in laddr, raddr;
socklen_t raddr_len;
char ipstr[IPSTRSIZE];
sd = socket(AF_INET, SOCK_STREAM, 0 /* IPPROTO_TCP IPPROTO_SCTP*/);
if(sd < 0){
perror("socket()");
exit(1);
}
int val = 1;
if(setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) < 0){
perror("setsockopt()");
exit(1);
}
laddr.sin_family = AF_INET;
laddr.sin_port = htons(atoi(SERVERPOT));
inet_pton(AF_INET, "127.0.0.1", &laddr.sin_addr);
if(bind(sd, (void *)&laddr, sizeof(laddr)) < 0){
perror("bind()");
exit(1);
}
if(listen(sd, 200) < 0){
perror("listen()");
exit(1);
}
raddr_len = sizeof(raddr);
while(1){
newsd = accept(sd, (void *)&raddr, &raddr_len);
if(newsd < 0){
perror("accept()");
exit(1);
}
pid = fork();
if(pid < 0){
perror("fork()");
exit(1);
}
if(pid == 0){
close(sd);
inet_ntop(AF_INET, &raddr.sin_addr, ipstr, IPSTRSIZE);
printf("Client:%s:%d\n", ipstr, ntohs(raddr.sin_port));
server_job(newsd);
close(newsd);
exit(0);
}
close(newsd);
}
close(sd);
}
- http 的使用
URL 和 FS 是映射关系
/ /var/www
/cgi-bin /var/www/cgi-bin
直接使用 http1.0 用程序拉取资源,下载 /var/www/html/test.jpg
webdl.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>
#define IPSTRSIZE 40
#define BUFSIZE 1024
int main(int argc, char* argv[]){
if(argc < 2){
printf("Usage...\n");
exit(1);
}
int sd, newsd;
FILE* fp;
long long stamp;
struct sockaddr_in laddr, raddr;
socklen_t raddr_len;
char ipstr[IPSTRSIZE];
char rbuf[BUFSIZE];
sd = socket(AF_INET, SOCK_STREAM, 0 /* IPPROTO_TCP IPPROTO_SCTP*/);
if(sd < 0){
perror("socket()");
exit(1);
}
raddr.sin_family = AF_INET;
raddr.sin_port = htons(80);
inet_pton(AF_INET, argv[1], &raddr.sin_addr);
if(connect(sd, (void *)&raddr, sizeof(raddr)) < 0){
perror("connect()");
exit(1);
}
//recv();
//close();
fp = fdopen(sd, "r+");
if(fp == NULL){
perror("fdopen()");
exit(1);
}
fprintf(fp, "GET /test.jpg\r\n\r\n");
fflush(fp);
int len;
FILE *fpp = fopen("dl.jpg", "w");
while (1)
{
len = fread(rbuf, 1, BUFSIZE, fp);
fwrite(rbuf, 1, len, fpp);
if(len <= 0){
break;
}
}
fclose(fpp);
fclose(fp);
exit(0);
}
- 静态进程池
静态进程池 server.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>
#include <wait.h>
#include "proto.h"
#define IPSTRSIZE 40
#define BUFSIZE 1024
#define PROCNUM 4
static void server_job(int sd){
char buf[BUFSIZE];
int len;
len = sprintf(buf, FMT_STAMP, (long long)time(NULL));
if(send(sd, buf, len, 0) < 0){
perror("send()");
exit(1);
}
}
static void server_loop(int sd);
int main(){
pid_t pid[PROCNUM];
int sd;
struct sockaddr_in laddr;
sd = socket(AF_INET, SOCK_STREAM, 0 /* IPPROTO_TCP IPPROTO_SCTP*/);
if(sd < 0){
perror("socket()");
exit(1);
}
int val = 1;
if(setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) < 0){
perror("setsockopt()");
exit(1);
}
laddr.sin_family = AF_INET;
laddr.sin_port = htons(atoi(SERVERPOT));
inet_pton(AF_INET, "127.0.0.1", &laddr.sin_addr);
if(bind(sd, (void *)&laddr, sizeof(laddr)) < 0){
perror("bind()");
exit(1);
}
if(listen(sd, 200) < 0){
perror("listen()");
exit(1);
}
for(int i = 0; i < PROCNUM; i++){
pid[i] = fork();
if(pid[i] < 0){
perror("fork()");
exit(1);
}
if(pid[1] == 0){
server_loop(sd);
exit(0);
}
}
for(int i = 0; i < PROCNUM; i++){
wait(NULL);
}
close(sd);
exit(0);
}
static void server_loop(int sd){
int newsd;
struct sockaddr_in raddr;
socklen_t raddr_len;
char ipstr[IPSTRSIZE];
raddr_len = sizeof(raddr);
while(1){
newsd = accept(sd, (void *)&raddr, &raddr_len);
if(newsd < 0){
perror("accept()");
exit(1);
}
inet_ntop(AF_INET, &raddr.sin_addr, ipstr, IPSTRSIZE);
printf("[%d]Client:%s:%d\n", getpid(), ipstr, ntohs(raddr.sin_port));
server_job(newsd);
close(newsd);
}
close(sd);
}