本章接上章Socket编程(一)/* SIDCHLD信号处理函数 */
vod sig_chld(int signo)
{
pid_t pid;
int stat;
while ((pid = waitpid(-1, &stat, WNOHANG)) > 0);
return;
}
[client]
sockfd=socket(AF_INET,SOCK_STREAM,0);
bzero(&servaddr, sizeof(servaddr));
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_familf=AF_INET;
servaddr.sin_port=htons(SERV_PORT);
inet_pton(AF_INET, SERV_IP_STRING, &servaddr.sin_addr);
connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(servaddr);
….//process
exit(0);
终止网络连接的正常方法是调用 close(),但是有两个限制:
1.将描述字的访问计数-1,到 0 时才激发 TCP 连接的终止序列 (丢弃接收
缓冲区里的数据,发送发送
缓冲区里的数据,如果描述字的访问计数为 0,在数据之后发出 FIN)
2. 终止了数据传输的两个方向,读和写shutdown可以关闭一半连接,而且在发完发送
缓冲区里的数据后立即发出 FIN 序列
UDP 无连接 编程时,在 SERVER 端少了 accept ()和 listen(),客户端少了 connect(),因为是无连接的,所以不用 close(),读写一般用 sendto()和 recvfrom()。如果 client发送的数据被路由丢弃或者服务器的应答信息丢失,那么 client 将一直阻塞,直到应用设置的超时到达,UDP 需要应用来做接受确认、传输超时或者重传的机制。
一般的,UDP服务属于迭代的,而 TCP 服务大多数是并发的。用 I/O 复用方式(select())实现(可以避免为每个客户端连接创建一个进程的开销):
[server]
int client[FD_SETSIZE];
fd_set allset, rset;
…
maxfd = listenfd;
maxi = -1;
for (I = 0; I < FD_SETSIZE; i++)
client[i] = -1;
FD_ZERO(&allset);
FD_SET(listenfd, &allset);
For (;;){
rset = allset;
nready = select(maxfd+1, &rset, NULL, NULL, NULL);
if (FD_ISSET(listenfd, &rset)) { //新连接
clilen = sizeof(cliaddr);
connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen);
for (i = 0; I < FD_SETSIZE; i++) {
if (client[i] < 0) { //找出 client 数组中第一个-1 的单元存放已经连接的socket
client[i] = connfd;
break;
}
if (I FD_SETSIZE)
fprintf(stderr, “too many clients\n”);
FD_SET(connfd, &allset);
If (connfd > maxfd)
Maxfd = connfd;
If (I > maxi)
Maxi = I;
If (--nready <= 0)
Continue;
}
for (I = 0; I <= maxi; i++) {
if ((sockfd = client[i]) < 0)
continue;
if (FD_ISSET(sockfd, &rset)) {
if ((n = read(sockfd, …)) 0){ //客户端已经关闭连接
close(sockfd);
FD_CLR(sockfd, &allset);
Client[i] = -1;
} else
{
… //process
if (--nready <=0 )
break;
}
}
}
}
用这种方法有一个问题:
容易受 DDOS攻击。如一个客户端发一个字节,就睡眠,该程序将阻塞在 read()上,无法再为其他合法的客户端服务。解决方法:
1.非阻塞 I/O
2. 为每个连接创建进程或线程
3. 对 I/O 操作设置操时
四、常用函数函数中均使用通用地址结构,所以一般要将特定协议的地址转换成通用地址结构。
1.从进程到内核
bind
connect
sendto
sendmsg
2.从内核到进程
Accept
Recvfrom
Recvmsg
Getpeername
Getsockname
3..解析器函数:
ethostbyname
gethostbyaddr
getservbyname 端口号是以网络字节序返回的
getservbyport 参数port必须是网络字节序,所以要调用htons(port num)
4.I/O流和描述字
fdopen():将描述字(int)转换为I/O 流(FILE *)
fileno():将 I/O 流转换为描述字
注意:虽然一个I/O流也是全双工的,但是读和写之间必须要有一些如fflush(),fseek(),fsetpos(),rewind()函数。最好的办法是对一个描述字创建两个流,读和写分开。
FILE *fin, *fout;
Fin = fdopen(sockfd, “r);
Fout = fdopen(sockfd, “w”);
Fgets(…);
Fputs(…);
但是这种标准 I/O 库的编程必须注意当前的缓冲方式:
完全缓冲:只有
缓冲区满、fflush()或进程 exit()时才输出I/O行缓冲
五、僵尸进程Zombie<defunct >进程:一个子进程终止,系统内核会发出 SIGCHLD 信号,如果程序中没有用singal、sigaction 进行SIG_IGN 处理,也没有用 wait、waitpid 进行等待,结束的子进程就会变为僵尸进程。
僵尸进程产生的目的是保存子进程的信息,以便父进程在以后某个时刻需要取回如果一个进程终止,但子进程有 Zombie 状态,这时他们的父进程将由 pid=1 的init 进程接管,用 wait来等待负责收回僵尸进程的资源。
僵尸进程占用内核空间
六、I/O模式5个I/O 模式:
1. 阻塞 I/O
2. 非阻塞 I/O
设置该标志代码:
int flags;
if ((flags = fcntl(fd, F_GETFL, 0)) < 0)
//出错处理
flags |= O_NONBLOCK;
if ((fcntl(fd, F_SETFL, flags)) < 0)
//出错处理
关闭该标志代码:
…flags &= ~O_NONBLOCK;
…
非阻塞 connect 的典型应用:
…//设置sockfd 为非阻塞,如上述代码
if ((n = connect(…)) < 0)
if (errno != EINPROCESS) //正常情况下,connect 连接有一定时间,应该返回EINPROCESS
return(-1);
if (n 0) //特殊情况,如 CLIENT和 SERVER 在同台主机上,连接快速返回
goto done;
FD_ZERO(&rset);
FD_SET(sockfd &rset);
West = rset;
Struct timeval val;
Val.tv_sec = …;
Val.tv_usec = 0;
If ((n = select(sockfd+1, &rset, &west, NULL, &val)) 0) { //超时
Errno = ETIMEOUT;
Close(sockfd);
Return(-1);
}
int error;
if (FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &west)) {
len = sizeof(error);
if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) //检查 SOCKET上待处理的错误
return(-1);
}else
//出错处理,表示在这段时间内没连接上 SERVER
done:
fcntl(sockfd, FD_SETFL, flags); //恢复为原来的模式
if (error){
close(sockfd);
errno = error;
return(-1);
}
return(0);
3. I/O 复用
4. 信号驱动 I/O(SIGIO)
5. 异步 I/O
前 4 个I/O模式都属于同步 I/O,因为它们都阻塞于 I/O 的操作(即 I/O 数据准备好时,从内核空间往用户空间拷贝数据)
七、守护进程在后台运行并且与终端脱离联系的进程,只要系统不停止就一直运行着,有几种方式启动:
1./etc/rcx.d 下的启动脚本
2.Inetd 启动
3.Cron 或者at 启动
守护进程的输出信息一般通过 SYSLOG 输出
函数示例:
#include <syslog.h >
#define MAXFD 64
void daemon_init()
{
int I;
pid_t pid;
if ((pid = fork()) != 0)
exit(0);
setsid():
signal(SIGHUP, SIG_IGN);
if ((Pid = fork()) != 0)
exit(0):
chdir(“/”);
umask(0);
for (i=0; i<MAXFD; i++)
close(i);
openlog(logname, LOG_PID, facility);
}
八、 I/O 超时方法 1:alarm()
例如:
static void connect_alarm(int signo)
{
return;
}
signal(SIGALARM, connect_alarm);
if (alarm(timeout_sec)!=0
//error
if (connect(sockfd, (struct sockaddr *)servaddr, servlen) < 0)
{
close(sockfd);
if (errno = EINTR)
errno = ETIMEDOUT;
}
alarm(0);
方法 2:select()
例如:
int readable_timeout(int fd, int sec)
{
fd_set rset;
struct timeval tv;
FD_ZERO(&rset);
FD_SET(fd, &rset):
Tv.tv_sec = sec;
Tv.tv_usec = 0;
Return(select(fd+1, &rset, NULL, NULL, &tv));
}
…
sendto(sockfd, …);
if (readable_timeout(sockfd, 5) 0)
//error
else
recvfrom(sockfd,…); //可以读
方法 3:SO_RCVTIMEO 和SO_SNDTIMEO套接口选项
例如:
struct timeval tv;
tv.tv_sec=5;
tv.tv_usec=0;
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)):
while (fgets(sendline…)!=NULL){
sendto(sockfd, …);
n=recvfrom(sockfd, …);
if (n<0)
if (errno EWOULEBLOCK) {
fprintf(stderr, “socket,timeout”);
continue;
}
else
//error;
…
九、辅助数据的应用可以运用于任何进程之间传递描述字的应用sendmsg 和 recvmsg 可以用来传送辅助数据(控制信息)。辅助数据由若干个辅助对象组成,每个辅助对象由一个 cmsghdr 结构开头。
struct msghdr {
void *msg_name; //用于 UDP 协议,发送方可以存放目的地址,接收方可以存放发送地址
size_t msg_namelen; //同上
struct iovec *msg_iov; //输入输出缓冲区数据,两个成员iov_base和iov_len
int msg_iovlen;
void *msg_control; // 辅助数据
size_t msg_controllen; //辅助数据大小
int msg_flags; //只对 recvmsg有用
};
Struct cmsghdr{
Socklen_t cmsg_len;
Int cmsg_level; //IPV4是 IPPROTO_IP,UNIX 域是 SOL_SOCKET
Int Cmsg_type; //在IPV4 中,IP_RECVDSTADDR 接受 UDP 数据报的目的地址
//IP_RECVIF 接受 UDP 数据报的接口索引
//UNIX 域中是 SCM_RIGHTS 传送描述字
//SCM_CREDS 传送用户凭证
}
//char cmsg_data[];
msghdr.msg_control 指向的辅助数据必须按 cmsghdr 结构对齐。利用 UNIX 域SOCKET 方式:
my_open(const char *pathname, int mode)
{
int sockfd[2];
…
socketpair(AF_LOCAL, SOCK_STREAM, 0, sockfd); //创建了一个流管道
if ((childpid = fork()) 0) //子进程
close(socdfd[0]);
snprintf(argsockfd, sizeof(argsocdfd), “%d”, sockfd[1]);
snprintf(argmode, sizeof(mode), “%d”, mode);
execl(“./openfile”, “openfile”, argsockfd, pathname, argmode, (char*)NULL);
perror(“exec error\n”);
}
close(sockfd[1]);
waitpid(chidpid, &status, 0);
if (WIFEXITED(status) 0)
perror(“chid process did not terminate\n”);
if ((status = WEXITSTATUS(status)) 0) //把终止状态转换为退出状态 0-255
read_fd(sockfd[0], &c, 1, &fd);
else{
errno = status;
fd = -1;
}
close(sockdf[0]);
return(fd);
}
ssize_t read_fd(int fd, void *ptr, ssize_t nbytes, int *recvfd)
{
struct msghdr msg; // 辅助数据对象
struct iovec iov[1];
union{
struct cmsghdr unit; //这里定义辅助数据对象的目的是为了让 msg_control 缓冲区和辅助数据单元对齐,所以不是简单地定义一个char control[…],而是定义一个union char control[CMSG_SPACE(sizeof(int))]; //辅助数据缓冲区
}
control_buf;
struct cmsghdr *unitptr;
int n;
msg.msg_control = control_buf.control; //辅助数据缓冲区
msg.msg_controllen = sizeof(control_buf.control); //辅助数据大小
msg.msg_name = NULL;
msg.msg_namelen = 0;
iov[0].iov_base = ptr;
iov[0].iov_len = nbytes;
msg.msg_iov = iov;
msg.msg_iovlen = 1;
if ((n = recvmsg(fd, &msg, 0) <= 0)
return(n);
if ((unitptr = CMSG_FIRSTHDR(&msg) != NULL && unitptr- >cmsg_len
CMSG_LEN(sizeof(int))) { //cmsg_len 应该=CMSG_LEN 宏得出的结果
if (unitptr- >cmsg_level != SOL_SOCKET) //UNIX DOMAIN用SOL-SOCKET
//error
if (unitptr- >cmsg_type != SCM_RIGHTS)
//error
*recvd = *((int *)CMSG_DATA(unitptr)); //获得该辅助对象的数据
}
else
*recvd = -1;
return(n);
}
子进程写程序摘录:
msg.msg_control = control_buf.control;
msg.msg_controllen = sizeof(control_buf.control);
unitprt = CMSG_FIRSTHDR(&msg);
unitptr- >cmsg_len = CMSG_LEN(sizeof(int));
unitptr- >cmsg_level = SOL_SOCKET;
unitptr- >cmsg_level = SCM_RIGHTS;
*((int *)CMSG_DATA(unitptr)) = sendfd; //CMSG_DATA()返回与 cmsghdr相关联的数据的第一个字节的指针
msg.msg_name = NULL;
msg.msg_namelen = 0;
iov[0].iov_base = ptr;
iov[0].iov_len = nbytes;
msg.msg_iov = iov;
msg.msg_iovlen = 1;
sendmsg(fd, &msg, 0);
用户凭证
#include <sys/ucred.h >
struct fcred{
uid_t fc_ruid; //真实UID
gid_t fc_rgid; //真实GID
char fc_login[MAXLOGNAME]; //LOGIN 名字
uid_t fc_uid; //有效 UID
short fc_ngroups; //组数
gid_t fc_groups[NGROUPS];
}
#define fc_gid fc_groups[0]; //有效 GID
需要将 LOCAL _CREDS 这个 SOCKET 选项设置为 1,在 TCP 中是 CLIENT 在连接后第一次发送数据时由内核一起发送的,UDP 是每次发送数据都产生
本章内容来自于互联网,小编只负责排版,因受小编技术所限,有时断句的地方并不准确,但小编已经尽力让本章整理的更有可读性,请谅解。因小编保存这篇文章时间过长,已记不清来源,所以未标注来源,敬请谅解!