网络套接字socket
基本功:两门语言,一门脚本语言。使用拿来主义先实现功能。
讨论:跨主机的传输需要注意的问题。
1、字节序问题:大端存储:低地址处放高字节
小端存储:X86 低地址处放低字节
0x00 00 00 05
大 05 00 00 00
小 00 00 00 05
区分主机字节序和网络字节序:
主机字节序:host
网络字节序:network
解决办法:to _s: htons ,htonl, ntohs,ntohl
2 对齐:
struct
{
int i;
float;
char ch;
} #占用12字节。
解决办法是不对齐。
3 类型长度问题:
int
char
解决:int32_t , uint32_t , int64_t , int8_t , uint8_t
4 socket 是什么?
在协议层和传输方式中间的介质。
SOCK_STREAM 有序的 可靠的(只要能接收到包,一定能保证数据是正确的) 双工的 基于连接的(点对点,一对一,每个人专用的) 字节流传输()。
SOCK_DGRAM 分组为单位,无连接的,不可靠的,和流式相反。
SOKC_SEQPACKET 安全、可靠、双工、基于连接、有序可靠的报式传输。
报式套接字:
程序猿能不能完成网络程序,看程序猿能不能完成报式套接字。
被动端(先运行):
1、取得SOCKET
2、给SOCKET取得地址
3、收/发消息
4、关闭SOCKET
主动端:
1、取得SOCKET
2、给SOCKET取得地址(可以省略)
3、收/发消息
4、关闭SOCKET
不同的协议族绑定的地址的结构体是不一样的。
socket();
bind();
sendto();
rcvfrom();
inet_pton();
inet_ntop();
setsockopt(int scokfd,int level,int optname,void *optval,socklen_t optlen);
getsockopt();
报式套接字基本实现
头文件.h
#ifndef PROTO_H__
#define PROTO_H__
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#define RCVPORT "1989"
#define NAMESIZE 11
struct msg_st
{
u_int8_t name[NAMESIZE];
u_int32_t math;
u_int32_t chinese;
};//字节不对齐
//__attribute_((packed));
#pragma pack()
#endif
发送.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include "proto.h"
int main(int argc,char **argv)
{
int sd;
struct msg_st sendbuf;
//远端地址,不是本端地址,是发送给谁
struct sockaddr_in raddr;
if (argc < 2)
{
fprintf(stderr,"Usage...\n");
exit(1);
}
sd = socket(AF_INET,SOCK_DGRAM,0);
if (sd<0)
{
perror("socket()");
exit(1);
}
// bind();
strcpy(sendbuf.name,"xiaohong");
sendbuf.chinese = htonl(rand()%100);
sendbuf.math = htonl(rand()%100);
raddr.sin_family = AF_INET;
raddr.sin_port = htons(atoi(RCVPORT));
inet_pton(AF_INET,argv[1],&raddr.sin_addr);
if(sendto(sd,&sendbuf,sizeof(sendbuf),0,(void *)&raddr,sizeof(raddr)) < 0)
{
perror("sendto()");
exit(1);
}
puts("ok!");
close(sd);
exit(0);
}
接收.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include "proto.h"
#define IPSTRSIZE 40
int main()
{
//APV4 默认支持报式套接字来实现,写0,相当于IPPRPOTO_UDP
int sd;
struct sockaddr_in laddr,raddr;
struct msg_st rcvbuf;
socklen_t raddr_len;
char ipstr[IPSTRSIZE];
sd = socket(AF_INET,SOCK_DGRAM,0);
if (sd<0)
{
perror("socket()");
exit(1);
}
laddr.sin_family = AF_INET;
//主机转网络,2个字节
laddr.sin_port = htons(atoi(RCVPORT));
//网络IP地址转换成大整数
inet_pton(AF_INET,"0.0.0.0",&laddr.sin_addr);
if (bind(sd,(void *)&laddr,sizeof(laddr)) < 0)
{
perror("bind()");
exit(1);
}
/*!!!!!!重要*/
raddr_len = sizeof(raddr);
while (1)
{
recvfrom(sd,&rcvbuf,sizeof(rcvbuf),0,(void *)&raddr,&raddr_len);
//网络转主机,2个字节
//大整数转换成点分式,argc 2 是 待转换的地址
inet_ntop(AF_INET,&raddr.sin_addr,ipstr,IPSTRSIZE);
printf("---MESSAGE FROM %s:%d---\n",ipstr,ntohs(raddr.sin_port));
printf("NAME = %s \n",rcvbuf.name);
printf("MATH = %d \n",ntohl(rcvbuf.math));
printf("CHINESE = %d \n",ntohl(rcvbuf.chinese));
}
close(sd);
exit(0);
}
改进版本-发送.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include "proto.h"
int main(int argc,char **argv)
{
int sd,size;
struct msg_st *sendbufp;
//远端地址,不是本端地址,是发送给谁
struct sockaddr_in raddr;
if (argc < 3)
{
fprintf(stderr,"Usage...\n");
exit(1);
}
if (strlen(argv[2])>NAMEMAX)
{
fprintf(stderr,"Name is too long!\n");
exit(1);
}
size = sizeof(struct msg_st) + strlen(argv[2]);
sendbufp = malloc(size);
if (sendbufp == NULL)
{
perror("malloc()");
exit(1);
}
sd = socket(AF_INET,SOCK_DGRAM,0);
if (sd<0)
{
perror("socket()");
exit(1);
}
strcpy(sendbufp->name,argv[2]);
sendbufp->chinese = htonl(rand()%100);
sendbufp->math = htonl(rand()%100);
raddr.sin_family = AF_INET;
raddr.sin_port = htons(atoi(RCVPORT));
inet_pton(AF_INET,argv[1],&raddr.sin_addr);
if(sendto(sd,sendbufp,size,0,(void *)&raddr,sizeof(raddr)) < 0)
{
perror("sendto()");
exit(1);
}
puts("ok!");
close(sd);
exit(0);
}
改进版本-接收.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include "proto.h"
#define IPSTRSIZE 40
int main()
{
//APV4 默认支持报式套接字来实现,写0,相当于IPPRPOTO_UDP
int sd,size;
struct sockaddr_in laddr,raddr;
struct msg_st *rcvbufp;
socklen_t raddr_len;
char ipstr[IPSTRSIZE];
size = sizeof(struct msg_st) + NAMEMAX -1;
rcvbufp = malloc(size);
if (rcvbufp == NULL)
{
perror("malloc()");
exit(1);
}
sd = socket(AF_INET,SOCK_DGRAM,0);
if (sd<0)
{
perror("socket()");
exit(1);
}
laddr.sin_family = AF_INET;
//主机转网络,2个字节
laddr.sin_port = htons(atoi(RCVPORT));
//网络IP地址转换成大整数
inet_pton(AF_INET,"0.0.0.0",&laddr.sin_addr);
if (bind(sd,(void *)&laddr,sizeof(laddr)) < 0)
{
perror("bind()");
exit(1);
}
/*!!!!!!重要*/
raddr_len = sizeof(raddr);
while (1)
{
recvfrom(sd,rcvbufp,size,0,(void *)&raddr,&raddr_len);
//网络转主机,2个字节
//大整数转换成点分式,argc 2 是 待转换的地址
inet_ntop(AF_INET,&raddr.sin_addr,ipstr,IPSTRSIZE);
printf("---MESSAGE FROM %s:%d---\n",ipstr,ntohs(raddr.sin_port));
printf("NAME = %s \n",rcvbufp->name);
printf("MATH = %d \n",ntohl(rcvbufp->math));
printf("CHINESE = %d \n",ntohl(rcvbufp->chinese));
}
close(sd);
exit(0);
}
多点通讯(广播(全网广播、子网广播)、多播/组播):
广播.h
#ifndef PROTO_H__
#define PROTO_H__
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#define MTGROUP "224.2.2.2"
#define RCVPORT "1989"
#define NAMESIZE 11
struct msg_st
{
u_int8_t name[NAMESIZE];
u_int32_t math;
u_int32_t chinese;
};//字节不对齐
//__attribute_((packed));
#pragma pack()
#endif
广播接收.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <net/if.h>
#include "proto.h"
#define IPSTRSIZE 40
int main()
{
//APV4 默认支持报式套接字来实现,写0,相当于IPPRPOTO_UDP
int sd;
struct sockaddr_in laddr,raddr;
struct msg_st rcvbuf;
socklen_t raddr_len;
char ipstr[IPSTRSIZE];
sd = socket(AF_INET,SOCK_DGRAM,0);
if (sd<0)
{
perror("socket()");
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_ADD_MEMBERSHIP,&mreq,sizeof(mreq))<0)
{
perror("setsockopt()");
exit(1);
}
laddr.sin_family = AF_INET;
//主机转网络,2个字节
laddr.sin_port = htons(atoi(RCVPORT));
//网络IP地址转换成大整数
inet_pton(AF_INET,"0.0.0.0",&laddr.sin_addr);
if (bind(sd,(void *)&laddr,sizeof(laddr)) < 0)
{
perror("bind()");
exit(1);
}
/*!!!!!!重要*/
raddr_len = sizeof(raddr);
while (1)
{
recvfrom(sd,&rcvbuf,sizeof(rcvbuf),0,(void *)&raddr,&raddr_len);
//网络转主机,2个字节
//大整数转换成点分式,argc 2 是 待转换的地址
inet_ntop(AF_INET,&raddr.sin_addr,ipstr,IPSTRSIZE);
printf("---MESSAGE FROM %s:%d---\n",ipstr,ntohs(raddr.sin_port));
printf("NAME = %s \n",rcvbuf.name);
printf("MATH = %d \n",ntohl(rcvbuf.math));
printf("CHINESE = %d \n",ntohl(rcvbuf.chinese));
}
close(sd);
exit(0);
}
广播发送.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <net/if.h>
#include <unistd.h>
#include <string.h>
#include "proto.h"
int main(int argc,char **argv)
{
int sd;
struct msg_st sendbuf;
struct ip_mreqn mreq;
//远端地址,不是本端地址,是发送给谁
struct sockaddr_in raddr;
sd = socket(AF_INET,SOCK_DGRAM,0);
if (sd<0)
{
perror("socket()");
exit(1);
}
//设置为多播模式,对当前socket属性做了一个更改
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("setsockopt()");
exit(1);
}
strcpy(sendbuf.name,"xiaohong");
sendbuf.chinese = htonl(rand()%100);
sendbuf.math = htonl(rand()%100);
raddr.sin_family = AF_INET;
raddr.sin_port = htons(atoi(RCVPORT));
inet_pton(AF_INET,MTGROUP,&raddr.sin_addr);
if(sendto(sd,&sendbuf,sizeof(sendbuf),0,(void *)&raddr,sizeof(raddr)) < 0)
{
perror("sendto()");
exit(1);
}
puts("ok!");
close(sd);
exit(0);
}
组播的实现
组播发送.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <net/if.h>
#include <unistd.h>
#include <string.h>
#include "proto.h"
int main(int argc,char **argv)
{
int sd;
struct msg_st sendbuf;
struct ip_mreqn mreq;
//远端地址,不是本端地址,是发送给谁
struct sockaddr_in raddr;
sd = socket(AF_INET,SOCK_DGRAM,0);
if (sd<0)
{
perror("socket()");
exit(1);
}
//设置为多播模式,对当前socket属性做了一个更改
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("setsockopt()");
exit(1);
}
strcpy(sendbuf.name,"xiaohong");
sendbuf.chinese = htonl(rand()%100);
sendbuf.math = htonl(rand()%100);
raddr.sin_family = AF_INET;
raddr.sin_port = htons(atoi(RCVPORT));
inet_pton(AF_INET,MTGROUP,&raddr.sin_addr);
if(sendto(sd,&sendbuf,sizeof(sendbuf),0,(void *)&raddr,sizeof(raddr)) < 0)
{
perror("sendto()");
exit(1);
}
puts("ok!");
close(sd);
exit(0);
}
组播接收.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <net/if.h>
#include "proto.h"
#define IPSTRSIZE 40
int main()
{
//APV4 默认支持报式套接字来实现,写0,相当于IPPRPOTO_UDP
int sd;
struct sockaddr_in laddr,raddr;
struct msg_st rcvbuf;
socklen_t raddr_len;
char ipstr[IPSTRSIZE];
sd = socket(AF_INET,SOCK_DGRAM,0);
if (sd<0)
{
perror("socket()");
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_ADD_MEMBERSHIP,&mreq,sizeof(mreq))<0)
{
perror("setsockopt()");
exit(1);
}
laddr.sin_family = AF_INET;
//主机转网络,2个字节
laddr.sin_port = htons(atoi(RCVPORT));
//网络IP地址转换成大整数
inet_pton(AF_INET,"0.0.0.0",&laddr.sin_addr);
if (bind(sd,(void *)&laddr,sizeof(laddr)) < 0)
{
perror("bind()");
exit(1);
}
/*!!!!!!重要*/
raddr_len = sizeof(raddr);
while (1)
{
recvfrom(sd,&rcvbuf,sizeof(rcvbuf),0,(void *)&raddr,&raddr_len);
//网络转主机,2个字节
//大整数转换成点分式,argc 2 是 待转换的地址
inet_ntop(AF_INET,&raddr.sin_addr,ipstr,IPSTRSIZE);
printf("---MESSAGE FROM %s:%d---\n",ipstr,ntohs(raddr.sin_port));
printf("NAME = %s \n",rcvbuf.name);
printf("MATH = %d \n",ntohl(rcvbuf.math));
printf("CHINESE = %d \n",ntohl(rcvbuf.chinese));
}
close(sd);
exit(0);
}
流式套接字
流式套接字基础版
客户端
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <time.h>
#include <netinet/ip.h> /* superset of previous */
#include "proto.h"
int main(int argc,char**argv)
{
int sd;
struct sockaddr_in raddr;
long long stamp;
FILE * fp;
if (argc < 2)
{
fprintf(stderr,"Usage...\n");
exit(1);
}
sd = socket(AF_INET,SOCK_STREAM,0/*默认IPPROTO_TCP,IPPRORO_SCTP*/); //套接字
if (sd<0)
{
perror("socket()");
exit(1);
}
//bind();
raddr.sin_family = AF_INET;
raddr.sin_port = htons(atoi(SERVERPORT));//点分式转换成大整数
inet_pton(AF_INET,argv[1],&raddr.sin_addr);
//连接成功
if (connect(sd,(void*)&raddr,sizeof(raddr))<0)
{
perror("connect()");
exit(1);
}
//文件系统精髓
fp = fdopen(sd,"r+");
if (fp == NULL)
{
perror("fdopen()");
exit(1);
}
if (fscanf(fp,FMT_STAMP,&stamp)<1)
{
fprintf(stderr,"Bad format!\n");
}
else
{
fprintf(stdout,"stamp = %lld\n",stamp);
}
fclose(fp);
//rcve();
//close();
exit(0);
}
服务器端
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <time.h>
#include <netinet/ip.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;
char ipstr[IPSTRSIZE];
socklen_t raddr_len;
struct sockaddr_in localaddr,rmaddr;
sd = socket(AF_INET,SOCK_STREAM,0/*默认IPPROTO_TCP,IPPRORO_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);
}
localaddr.sin_family = AF_INET;//协议族
inet_pton(AF_INET,"0.0.0.0",&localaddr.sin_addr);//指定的IP地址转换成大整数,存放在localaddr.sin_addr
localaddr.sin_port = htons(atoi(SERVERPORT));//端口
//绑定我端的端口和我端的IP
if (bind(sd,(void*)&localaddr,sizeof(localaddr)) < 0) //连接
{
perror("bind()");
exit(1);
}
if(listen(sd,200)<0) //监听,第二个参数是全连接队列的大小
{
perror("listen()");
exit(1);
}
raddr_len = sizeof(rmaddr);
while (1)
{
//防止前功尽弃,使用newsd
newsd = accept(sd,(void *)&rmaddr,&raddr_len);
if(newsd<0) //接受连接
{
perror("accept()");
exit(1);
}
inet_ntop(AF_INET,&rmaddr.sin_addr,ipstr,IPSTRSIZE);//将大整数转换成点串,存放在ipstr中
/*网上接收来的数据进行转换*/
printf("Client: %s :%d\n",ipstr,ntohs(rmaddr.sin_port));
server_job(newsd);
close(newsd);
}
//一定执行不到。。。
close(sd);
exit(0);
}
协议端
#ifndef __PROTO_H__
#define __PROTO_H__
#define SERVERPORT "1989"
#define FMT_STAMP "%lld\n" //发出时戳的格式
#endif
流式套接字增强版
客户端
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <time.h>
#include <netinet/ip.h> /* superset of previous */
#include "proto.h"
int main(int argc,char**argv)
{
int sd;
struct sockaddr_in raddr;
long long stamp;
FILE * fp;
if (argc < 2)
{
fprintf(stderr,"Usage...\n");
exit(1);
}
sd = socket(AF_INET,SOCK_STREAM,0/*默认IPPROTO_TCP,IPPRORO_SCTP*/); //套接字
if (sd<0)
{
perror("socket()");
exit(1);
}
//bind();
raddr.sin_family = AF_INET;
raddr.sin_port = htons(atoi(SERVERPORT));//点分式转换成大整数
inet_pton(AF_INET,argv[1],&raddr.sin_addr);
//连接成功
if (connect(sd,(void*)&raddr,sizeof(raddr))<0)
{
perror("connect()");
exit(1);
}
//文件系统精髓
fp = fdopen(sd,"r+");
if (fp == NULL)
{
perror("fdopen()");
exit(1);
}
if (fscanf(fp,FMT_STAMP,&stamp)<1)
{
fprintf(stderr,"Bad format!\n");
}
else
{
fprintf(stdout,"stamp = %lld\n",stamp);
}
fclose(fp);
//rcve();
//close();
exit(0);
}
服务器端
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <time.h>
#include <netinet/ip.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;
pid_t pid;
char ipstr[IPSTRSIZE];
socklen_t raddr_len;
struct sockaddr_in localaddr,rmaddr;
sd = socket(AF_INET,SOCK_STREAM,0/*默认IPPROTO_TCP,IPPRORO_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);
}
localaddr.sin_family = AF_INET;//协议族
inet_pton(AF_INET,"0.0.0.0",&localaddr.sin_addr);//指定的IP地址转换成大整数,存放在localaddr.sin_addr
localaddr.sin_port = htons(atoi(SERVERPORT));//端口
//绑定我端的端口和我端的IP
if (bind(sd,(void*)&localaddr,sizeof(localaddr)) < 0) //连接
{
perror("bind()");
exit(1);
}
if(listen(sd,200)<0) //监听,第二个参数是全连接队列的大小
{
perror("listen()");
exit(1);
}
raddr_len = sizeof(rmaddr);
while (1)
{
//防止前功尽弃,使用newsd
newsd = accept(sd,(void *)&rmaddr,&raddr_len);
if(newsd<0) //接受连接
{
perror("accept()");
exit(1);
}
//父进程accept给子进程
pid = fork();
if (pid<0)
{
perror("fork()");
exit(1);
}
if (pid == 0) //子进程干活
{
//子进程只用newsd 不用sd
close(sd);
inet_ntop(AF_INET,&rmaddr.sin_addr,ipstr,IPSTRSIZE);//将大整数转换成点串,存放在ipstr中
/*网上接收来的数据进行转换*/
printf("Client: %s :%d\n",ipstr,ntohs(rmaddr.sin_port));
server_job(newsd);
close(newsd);
exit(0); //干完活跑路
}
//父进程只用sd 不用newsd
close(newsd);
}
//一定执行不到。。。
close(sd);
exit(0);
}
协议端
#ifndef __PROTO_H__
#define __PROTO_H__
#define SERVERPORT "1989"
#define FMT_STAMP "%lld\n" //发出时戳的格式
#endif
静态进程池
客户端
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <time.h>
#include <netinet/ip.h> /* superset of previous */
#include "proto.h"
int main(int argc,char**argv)
{
int sd;
struct sockaddr_in raddr;
long long stamp;
FILE * fp;
if (argc < 2)
{
fprintf(stderr,"Usage...\n");
exit(1);
}
sd = socket(AF_INET,SOCK_STREAM,0/*默认IPPROTO_TCP,IPPRORO_SCTP*/); //套接字
if (sd<0)
{
perror("socket()");
exit(1);
}
//bind();
raddr.sin_family = AF_INET;
raddr.sin_port = htons(atoi(SERVERPORT));//点分式转换成大整数
inet_pton(AF_INET,argv[1],&raddr.sin_addr);
//连接成功
if (connect(sd,(void*)&raddr,sizeof(raddr))<0)
{
perror("connect()");
exit(1);
}
//文件系统精髓
fp = fdopen(sd,"r+");
if (fp == NULL)
{
perror("fdopen()");
exit(1);
}
if (fscanf(fp,FMT_STAMP,&stamp)<1)
{
fprintf(stderr,"Bad format!\n");
}
else
{
fprintf(stdout,"stamp = %lld\n",stamp);
}
fclose(fp);
//rcve();
//close();
exit(0);
}
服务器端
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <time.h>
#include <netinet/ip.h>
#include "proto.h"
#define IPSTRSIZE 40
#define BUFSIZE 1024
#define PROCNUM 4
static void server_loop(int sd);
/*每次有人和我正常连接我做的事情,是把当前返回打时戳发送出去*/
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,i;
struct sockaddr_in localaddr;
pid_t pid;
sd = socket(AF_INET,SOCK_STREAM,0/*默认IPPROTO_TCP,IPPRORO_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);
}
localaddr.sin_family = AF_INET;//协议族
inet_pton(AF_INET,"0.0.0.0",&localaddr.sin_addr);//指定的IP地址转换成大整数,存放在localaddr.sin_addr
localaddr.sin_port = htons(atoi(SERVERPORT));//端口
//绑定我端的端口和我端的IP
if (bind(sd,(void*)&localaddr,sizeof(localaddr)) < 0) //连接
{
perror("bind()");
exit(1);
}
if(listen(sd,200)<0) //监听,第二个参数是全连接队列的大小
{
perror("listen()");
exit(1);
}
for (i = 0; i < PROCNUM; i++)
{
pid = fork();
if (pid<0)
{
perror("fork()");
exit(1);
}
if (pid == 0)
{
server_loop(sd);//子进程永远干活
exit(0);
}
for (i = 0; i < PROCNUM; i++)
{
wait(NULL);
}
close(sd);
exit(0);
}
}
static void server_loop(int sd)
{
socklen_t raddr_len;
struct sockaddr_in localaddr,rmaddr;
int newsd;
char ipstr[IPSTRSIZE];
raddr_len = sizeof(rmaddr);
while (1)
{
//防止前功尽弃,使用newsd
newsd = accept(sd,(void *)&rmaddr,&raddr_len);
if(newsd<0) //接受连接
{
perror("accept()");
exit(1);
}
inet_ntop(AF_INET,&rmaddr.sin_addr,ipstr,IPSTRSIZE);//将大整数转换成点串,存放在ipstr中
/*网上接收来的数据进行转换*/
printf("[%d] Client: %s :%d\n",getpid(),ipstr,ntohs(rmaddr.sin_port));
server_job(newsd);
close(newsd);
}
//一定执行不到。。。
close(sd);
}
协议端
#ifndef __PROTO_H__
#define __PROTO_H__
#define SERVERPORT "1989"
#define FMT_STAMP "%ld\n" //发出时戳的格式
#endif
动态进程池
与静态线程池差别仅仅在服务器端
服务器端(难点*****)
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <signal.h>
#include <time.h>
#include <errno.h>
#include <sys/mman.h>
#include <unistd.h>
#include <arpa/inet.h>
#include "proto.h"
//最小的空闲个数
#define MINSPARESERVER 5
#define MAXSPARESERVER 10
#define MAXCLIENTS 20
#define SIG_NOTIFY SIGUSR2 //预留出来的定义行为的信号
#define LINEBUFSIZE 80
#define IPSTRSIZE 40
#define BUFSIZE 1024
enum
{
STATE_IDEL = 0,
STATE_BUSY
};
struct sever_st
{
pid_t pid;
int state;
//int reuse;
};
//定义动态线程池,存放在堆上
static struct sever_st *serverpool;
static int idle_count = 0,busy_count = 0;
static int sd;
static void server_job(int pos)
{
int ppid,client_sd,len;
time_t stamp;
char linebuf[LINEBUFSIZE];
char ipstr[IPSTRSIZE];
ppid = getppid();
socklen_t raddr_len;
struct sockaddr_in raddr;
while (1)
{
serverpool[pos].state = STATE_IDEL;
kill(ppid,SIG_NOTIFY); //通知父进程看一眼
client_sd = accept(sd,(void *)&raddr,&raddr_len);
if (client_sd<0)
{
if (errno != EINTR || errno != EAGAIN)
{
perror("accept()");
exit(1);
}
}
//设置当前状态为忙态
serverpool[pos].state = STATE_BUSY;
kill(ppid,SIG_NOTIFY); //通知父进程看
//inet_ntop(AF_INET,&raddr.sin_addr,ipstr,IPSTRSIZE);
//测试程序
//printf("[%d]client:%s:%d",getpid(),ipstr,ntohs(raddr.sin_port));
stamp = time(NULL);
//写入到字符串中
len = snprintf(linebuf,LINEBUFSIZE,FMT_STAMP,stamp);
send(client_sd,linebuf,len,0);
/* if error */
sleep(3);
close(client_sd);
}
}
//工作代码
static int add_1_server(void)
{
int slot;
pid_t pid;
//到达最大值
if (idle_count + busy_count >= MAXCLIENTS)
return -1;
for (slot = 0; slot < MAXCLIENTS; slot++)
if (serverpool[slot].pid == -1)
break;
serverpool[slot].state = STATE_IDEL;
pid = fork();
if (pid<0)
{
perror("fork()");
exit(1);
}
if (pid == 0)
{
server_job(slot);//子进程永远干活
exit(0);
}
else
{
serverpool[slot].pid = pid;
idle_count ++;
}
return 0;
}
static int del_1_server(void)
{
int i;
if (idle_count == 0)
return -1;
for ( i = 0; i < MAXCLIENTS; i++)
{
if (serverpool[i].pid != -1 && serverpool[i].state == STATE_IDEL)
{
kill(serverpool[i].pid,SIGTERM);//杀掉进程
serverpool[i].pid = -1;
idle_count --;
break;
}
}
return 0;
}
static void usr2_handlers(int s)
{
return;
}
//遍历状态池
static int scanf_pool(void)
{
int i,busy = 0,idle = 0;
for ( i = 0; i < MAXCLIENTS; i++)
{
if (serverpool[i].pid == -1)
continue;
if (kill(serverpool[i].pid,0))//检测serverpool的i空间 pid是否存在
{
serverpool[i].pid = -1;
continue;
}
if (serverpool[i].pid == STATE_IDEL)//检测serverpol的i空间 pid是否存在
{
idle++;
}
else if (serverpool[i].pid == STATE_BUSY)
{
busy++;
}
else
{
fprintf(stderr,"Unknown statae.\n");
//_exit(1);
abort();
}
}
idle_count = idle;
busy_count = busy;
return 0;
}
int main()
{
int sd,i,len,val = 1;
struct sockaddr_in localaddr;
pid_t pid;
struct sigaction sa,osa;
sigset_t set,oset;
//让子进程自行消亡,
sa.sa_handler = SIG_IGN;//父进程阻塞去收尸,是这个信号去通知的,所以先把它忽略掉,防止父进程收尸
sigemptyset(&sa.sa_mask);//没有信号同用一个信号处理函数
sa.sa_flags = SA_NOCLDWAIT; //阻止子进程变成僵尸状态,免去收僵尸的困扰
sigaction(SIGCHLD,&sa,&osa);//对一个信号来做一个行为的指定,把之前的行为放到osa中。
sa.sa_handler = usr2_handlers;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIG_NOTIFY,&sa,&osa);
//阻塞信号
sigemptyset(&set);//集合清成空集
sigaddset(&set,SIG_NOTIFY);
//把set阻塞住,别的状态保存在oset中
sigprocmask(SIG_BLOCK,&set,&oset);
//自动分配空间,读写,共享,匿名映射, 也就相当于malloc
serverpool = mmap(NULL,sizeof(struct sever_st)*MAXCLIENTS,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1,0);
if (serverpool == MAP_FAILED)
{
perror("mmap()");
exit(1);
}
//初始化
for (int i = 0; i < MAXCLIENTS; i++)
{
serverpool[i].pid = -1;
}
sd = socket(AF_INET,SOCK_STREAM,0/*默认支持流式套接字IPPROTO_TCP,IPPRORO_SCTP*/); //套接字
if (sd<0)
{
perror("socket()");
exit(1);
}
/*为了解决随时可以使用用过的地址*/
if(setsockopt(sd, SOL_SOCKET,SO_REUSEADDR,&val,sizeof(val))<0)
{
perror("setsockopt()");
exit(1);
}
int val = 1;
/*为了解决随时可以使用用过的地址*/
if(setsockopt(sd, SOL_SOCKET,SO_REUSEADDR,&val,sizeof(val))<0)
{
perror("setsockopt()");
exit(1);
}
localaddr.sin_family = AF_INET;//协议族
inet_pton(AF_INET,"0.0.0.0",&localaddr.sin_addr);//指定的IP地址(点分式)转换成大整数,存放在localaddr.sin_addr
localaddr.sin_port = htons(atoi(SERVERPORT));//端口
//绑定我端的端口和我端的IP
if (bind(sd,(void*)&localaddr,sizeof(localaddr)) < 0) //连接
{
perror("bind()");
exit(1);
}
if(listen(sd,100)<0) //监听,第二个参数是全连接队列的大小
{
perror("listen()");
exit(1);
}
for (i = 0; i < MINSPARESERVER; i++)
{
add_1_server();
}
while (1)
{
sigsuspend(&oset);//信号驱动程序
scanf_pool();
//control the pool;
if (idle_count > MAXSPARESERVER)
{
for (i = 0; i < (idle_count - MAXSPARESERVER); i++)
del_1_server();//一次减少一个sever端口
}
else if(idle_count<MINSPARESERVER)
{
for (i = 0; i < (MINSPARESERVER - idle_count); i++)
add_1_server();//一次增加一个sever端口
}
//printf the pool
for ( i = 0; i < MAXCLIENTS; i++)
{
if (serverpool[i].pid == -1)
{
putchar(' ');
}
else if(serverpool[i].state == STATE_IDEL)
{
putchar('.');
}
else
{
putchar('x');
}
putchar('\n');
}
}
//恢复信号
sigprocmask(SIG_SETMASK,&oset,NULL);
}