欢迎来到.net学习网

欢迎联系站长一起更新本网站!QQ:879621940

您当前所在位置:首页 » ASP.Net » 正文

热门阅读

Socket编程(二)

创建时间:2011年12月10日 21:47  阅读次数:(6182)
分享到:
本章接上章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 是每次发送数据都产生

本章内容来自于互联网,小编只负责排版,因受小编技术所限,有时断句的地方并不准确,但小编已经尽力让本章整理的更有可读性,请谅解。因小编保存这篇文章时间过长,已记不清来源,所以未标注来源,敬请谅解!

来源:
说明:所有来源为 .net学习网的文章均为原创,如有转载,请在转载处标注本页地址,谢谢!
【编辑:Wyf

打赏

取消

感谢您的支持,我会做的更好!

扫码支持
扫码打赏,您说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

最新评论

共有评论0条
  • 暂无任何评论,请留下您对本文章的看法,共同参入讨论!
发表评论:
留言人:
内  容:
请输入问题 0+4=? 的结果(结果是:4)
结  果: