工作倒是写了不少的socket程序,还蛮有意思的,有必要总结一下
1. socket函数类型总结
socket函数类型图
SOCK_DGRAM就是UDP,SOCK_STREAM就是TCP
SOCK_RAW下面会提到
如果创建socket时指定为AF_UNIX(AF_UNIX宏与旧版的AF_LOCAL等同)时就为linux内部的socket,仅限于同一台计算机内的socket进程通讯,他使用的地址结构是struct sockaddr_un
而如果指定为AF_INET时则为unix和linux的网络socket,使用的数据结构则是struct sockaddr_in
不管是sockaddr_un/AF_UNIX还是sockaddr_in/AF_INET当IP为localhost的时候,由于可以使用localhost环回地址,数据不用经过物理网卡,多个的话,就用port不同来区别
2. socket服务器处理方式
循环服务器(最普通那种)
并发服务器(就是每一个链接一个线程),下面代码功能是:这个线程做一个server,另外做一个client,client连接过来后有一个终端窗口,因为很多进程都是后台运行的,有了终端窗口方便调试用的,在工作中很适用的功能,这里就是连接起一个线程
/*
* Function: dbg_server
* Purpose: for debug server
* Arguments:
* Returns:
*/
void dbg_server(void* arg)
{
int serv_fd;
int conn_fd;
struct sockaddr_un serv_addr;
int rc;
pthread_t pid;
char cmd[100];
if ((serv_fd = socket(AF_UNIX, SOCK_STREAM, NULL)) == -1)
{
perror("socket");
return;
}
bzero(&serv_addr, sizeof(serv_addr));
serv_addr.sun_family = AF_UNIX;
unlink(arg);/* if exit delete it */
strcpy(serv_addr.sun_path, arg);
if (bind(serv_fd, (struct sockaddr *) &serv_addr, sizeof(struct sockaddr)) == -1)
{
perror("bind");
return;
}
if (listen(serv_fd, 100) == -1)
{
perror("listen");
return;
}
for (; ;)
{
conn_fd = accept(serv_fd, NULL, NULL);
printf("accept conn_addr:0x%x/n", conn_fd);
rc = pthread_create(&pid, NULL, (void *) &dbg_client,
(void *) &conn_fd);
if (rc < 0)
{
printf("error pthread_create/n");
return;
}
}
}
并发多路复用,在UT通信产品中板卡间通信中用到的,这个是写的一份和上面同样的功能,对比不停的创建线程或者进程而言,更值得推荐
/*
* Function: dbg_server1
* Purpose: for debug server
* Arguments:
* Returns:
*/
void dbg_server1(void* arg)
{
struct sockaddr_un serv_addr;
struct sockaddr_un conn_addr;
int serv_fd, i, maxfd, conn_fd, n, clie_num = 0;
fd_set allset;
socklen_t conn_len;
char buf[1500];
int rc;
char cmd[100];
for (i = 0; i < FD_SETSIZE; i++)
{
clib_ipc_data[i].clie_fd = -1;
}
if ((serv_fd = socket(AF_UNIX, SOCK_STREAM, NULL)) == -1)
{
perror("socket");
}
bzero(&serv_addr, sizeof(serv_addr));
serv_addr.sun_family = AF_UNIX;
unlink(arg);/* if exit delete it */
strncpy(serv_addr.sun_path, arg, sizeof(serv_addr.sun_path) - 1);//strcpy(serv_addr.sun_path, arg);
if (bind(serv_fd, (struct sockaddr_un *) &serv_addr, sizeof(serv_addr)) == -1)
{
perror("bind");
}
if (listen(serv_fd, 100) == -1)
{
perror("listen");
}
FD_ZERO(&allset);
if (serv_fd != -1)
{
FD_SET(serv_fd, &allset);
}
maxfd = serv_fd;
conn_len = sizeof(conn_addr);
for (; ;)
{
rc = select(maxfd + 1, &allset, NULL, NULL, NULL);
if (rc < 0)
{
perror("select");
exit(1);
}
if (FD_ISSET(serv_fd, &allset))
{
conn_fd = accept(serv_fd, (struct sockaddr_un *) &conn_addr,
&conn_len);
printf("accept conn_addr/n");
for (i = 0; i < FD_SETSIZE; i++)
{
if (clib_ipc_data[i].clie_fd == -1)
{
clib_ipc_data[i].clie_fd = conn_fd;
clib_ipc_data[i].out = fdopen(conn_fd, "r+");
FD_SET(conn_fd, &allset);
if (clie_num < (i + 1))
{
clie_num = i + 1;
}
break;
}
}
if (conn_fd > maxfd)
{
maxfd = conn_fd;
}
}
for (i = 0; i < clie_num; i++)
{
if (clib_ipc_data[i].clie_fd != -1)
{
if (FD_ISSET(clib_ipc_data[i].clie_fd, &allset))
{
rc = read(clib_ipc_data[i].clie_fd, buf, 1500);
printf("buffer %s/n", buf);
printf("write msg to client:0x%x/n", i);
cli_debug(buf);
}
}
}
}
}
2.6提供了效率更高的epool,和select方式很像,但是多了一个epoll_event的结构来记录client的信息,我用linux自带的ab命令建50个连接测试,没感觉到效率有多大提高,那些网络公司的研究这个应该比较深
3. SOCK_RAW
sockfd=socket(PF_PACKET,SOCK_RAW,protocol);
sockfd=socket(PF_PACKET,SOCK_DGRAM,protocol);
SOCK_RAW和 SOCK_DGRAM类型套接字使用一种与设备无关的标准物理层地址结构sockaddr_ll,但具体操作的报文格式不同。 SOCK_RAW套接字直接14+20+*
SOCK_DGRAM套接字就没有那个14,只有20+*
因为telnet,ftp等密码都是明文的,所以可以linsniffer这个小工具抓到密码的,记得网卡一定要设置为混杂模式,下面是最主要的一段的代码
int openintf(char *d)
{
int fd;
struct ifreq ifr;
int s;
fd=socket(PF_PACKET, SOCK_RAW, ETH_P_IP);
/* 嵌套类型设置,并且只要IP包,如果是ETH_P_ALL就是所有的包 */
if(fd < 0)
{
perror("cant get SOCK_PACKET socket");
exit(0);
}
strcpy(ifr.ifr_name, d);
s=ioctl(fd, SIOCGIFFLAGS, &ifr);
if(s < 0)
{
close(fd);
perror("cant get flags");
exit(0);
}
ifr.ifr_flags |= IFF_PROMISC;
s=ioctl(fd, SIOCSIFFLAGS, &ifr);
/*网卡设置为混杂模式,只有这样才可以抓包括网络头在内的包 */
if(s < 0) perror("cant set promiscuous mode");
return fd;
}
所以现在比较推荐的连接是SSH,服务器端生成一个用户的私钥,拷贝出来,用putty之类的,加载这个私钥,直接连接服务器,协议没研究过,毕竟是加密的数据,服务器端应该需要解密的算法吧
tcpdump也是基于SOCK_RAW来实现的,早的linux版本还用 fd=socket(AF_INET, SOCK_PACKET, htons(0x800));这样的用法已经被fd=socket(PF_PACKET, SOCK_RAW, htons(0x800));替代了
4. 小结
其实一直疑惑那些网站后台都是怎么设计的呢?后台也是用select的吗?如果成千上万的连接,select支持最多句柄FD_SETSIZE也就1024啊?不知道N多的服务器之间是怎么建立起来的