Ⅰ linux C++如何使用套接字
包含头文件就可以了。socket的api都是C形式的,函数都是声明好的,头文件包含好,就能用了。锐英源有专业的视频,有兴趣可以关注下。
Ⅱ linux下c++套接字编程问题
accept(sockfd,(struct sockaddr *)&their_addr,sizeof(struct sockaddr));
linux socket不支持 sin_size, unix才支持
推荐你看一下unix socket编程
Ⅲ 求问:linux网络编程套接字如何实现同时实时收发
我现在用了套接字编程,我看书上介绍了TCP
IP之类的协议,那么套接字已经把这些协议包装好了,我们还要了解协议干什么,所以我想,linux网络编程是不是不只是套接字,是不是还有其他的一些东西,求教,真是迷茫了
没分了
谢谢啊
但是要求
客户端一直向服务器发数据,但是服务器可能偶尔发数据给客户点,怎么做才能使两者不受影响呢?能不能用一个套接字描述符实现呢?
我是想这样,先建立套接字,然后主线程一只向服务器发送数据,然后在建立一个线程,用前面建立的套接字接收服务器的数据,这样可行吗
谢谢了
Ⅳ linux中关于流式套接字编程代码的解释,求大神把每行代码加上注释,具体一点
服务器端
#include<sys/types.h>
#include<sys/socket.h>
#include<stdio.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<stdlib.h>
intmain(intargc,char*argv)
{
intserver_sockfd,client_sockfd;
intserver_len,client_len;
structsockaddr_inserver_address;
structsockaddr_inclient_address;
//获得一个socket文件描述符,使用tcpip协议
server_sockfd=socket(AF_INET,SOCK_STREAM,0);
server_address.sin_family=AF_INET;
//设置服务器端接受任何主机的请求
server_address.sin_addr.s_addr=htonl(INADDR_ANY);
//使用端口9734来接受请求
server_address.sin_port=htons(9734);
server_len=sizeof(server_address);
//绑定socket
bind(server_sockfd,(structsockaddr*)&server_address,server_len);
//监听连接请求,连接请求队列大小为5
listen(server_sockfd,5);
while(1)
{
charch;
printf("serverwaiting ");
client_len=sizeof(client_address);
//接受client端的连接请求,如果没有连接请求,该进程将阻塞
client_sockfd=accept(server_sockfd,(structsockaddr*)&client_address,&client_len);
//读数据
read(client_sockfd,&ch,1);
ch++;
//写数据
write(client_sockfd,&ch,1);
//关闭与client端的连接
close(client_sockfd);
}
}
客户端
#include<sys/types.h>
#include<sys/socket.h>
#include<stdio.h>
#include<stdlib.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
intmain(intargc,char*argv)
{
intsockfd;
intlen;
structsockaddr_inaddress;
intresult;
charch='A';
//设置需要连接服务器的ipport
sockfd=socket(AF_INET,SOCK_STREAM,0);
address.sin_family=AF_INET;
address.sin_addr.s_addr=inet_addr("127.0.0.1");
address.sin_port=htons(9734);
len=sizeof(address);
//连接服务器
result=connect(sockfd,(structsockaddr*)&address,len);
if(result==-1)
{
perror("oops:client3");
exit(1);
}
//写数据
write(sockfd,&ch,1);
//读数据
read(sockfd,&ch,1);
printf("charfromserver=%c ",ch);
//关闭与服务器的连接
close(sockfd);
exit(0);
}
关于你的问题:
1.简单的方法,设置socket套接字为非阻塞模式,然后轮询,并查看是否超时。
或者是使用select poll 等方法 ,设置超时时间。
或者 使用 alarm 信号,都可以。
2..每当接收到一个client 端的连接请求,就开辟一个线程为之服务。
或者使用select poll epoll等方法,推荐这个。
3.你是指的是处理信号吗?这个有专门的函数 signal 可以处理。
Ⅳ 求linux socket网络编程代码
Linux是多任务的操作系统,可在运行在Intel 80386及更高档次的PC机、ARMS、MIPS和PowerPC等多种计算机平台,已成为应用广泛、可靠性高、功能强大的计算机操作系统,Linux具有内核小、效率高、源代码开放等优点,还内含了TCP/IP网络协议,很适合在服务器领域使用,而服务器主要用途之一就是进行网络通信,随着计算机办公自动化处理技术的应用与推广,网络的不断普及,传统的纸张式文件传输方式已经不再适合发展的需要,人们更期待一种便捷、高效、环保、安全的网络传输方式.
协议概述TCP/IP即传输控制协议/网络协议[1](Transmission Control Protocol/Internet Protocol),是一个由多种协议组成的协议族,他定义了计算机通过网络互相通信及协议族各层次之间通信的规范,图1描述了Linux对IP协议族的实现机制[2]。
Linux支持BSD的套接字和全部的TCP/IP协议,是通过网络协议将其视为一组相连的软件层来实现的,BSD套接字(BSD Socket)由通用的套接字管理软件支持,该软件是INET套接字层,用来管理基于IP的TCP与UDP端口到端口的互联问题,从协议分层来看,IP是网络层协议,TCP是一个可靠的端口到端口的传输层协议,他是利用IP层进行传接报文的,同时也是面向连接的,通过建立一条虚拟电路在不同的网路间传输报文,保证所传输报文的无丢失性和无重复性。用户数据报文协议(User Datagram Protocol,UDP)也是利用IP层传输报文,但他是一个非面向连接的传输层协议,利用IP层传输报文时,当目的方网际协议层收到IP报文后,必须识别出该报文所使用的上层协议(即传输层协议),因此,在IP报头上中,设有一个"协议"域(Protocol)。通过该域的值,即可判明其上层协议类型,传输层与网络层在功能说的最大区别是前者提供进程通信能力,而后者则不能,在进程通信的意义上,网络通信的最终地址不仅仅是主机地址,还包括可以描述进程的某种标识符,为此,TCP/UDP提出了协议端口(Protocol Port)的概念,用于标识通信的进程,例如,Web服务器进程通常使用端口80,在/etc/services文件中有这些注册了的端口地址。
对于TCP传输,传输节点间先要建立连接,然后通过该连接传输已排好序的报文,以保证传输的正确性,IP层中的代码用于实现网际协议,这些代码将IP头增加到传输数据中,同时也把收到的IP报文正确的传送到TCP层或UDP层。TCP是一个面向连接协议,而UDP则是一个非面向连接协议,当一个UDP报文发送出去后,Linux并不知道也不去关心他是否成功地到达了目的的主机,IP层之下,是支持所有Linux网络应用的网络设备层,例如点到点协议(Point to Point Protocol,PPP)和以太网层。网络设备并非总代表物理设备,其中有一些(例如回送设备)则是纯粹的软件设备,网络设备与标准的Linux设备不同,他们不是通过Mknod命令创建的,必须是底层软件找到并进行了初始化之后,这些设备才被创建并可用。因此只有当启动了正确设置的以太网设备驱动程序的内核后,才会有/dev/eth0文件,ARP协议位于IP层和支持地址解析的协议层之间。
网络通信原理所有的网络通信就其实现技术可以分为两种,线路交换和包交换,计算机网络一般采用包交换,TCP使用了包交换通信技术,计算机网络中所传输的数据,全部都以包(Packet)这个单位来发送,包由"报头"和"报文"组成,结构如图2所示,在"报头"中记载有发送主机地址,接收主机地址及与报文内容相关的信息等,在"报文"中记载有需要发送的数据,网络中的每个主机和路由器中都有一个路由寻址表,根据这个路由表,包就可以通过网络传送到相应的目的主机。
网络通信中的一个非常重要的概念就是套接字(Socket)[3,4],简单地说,套接字就是网络进程的ID,网络通信归根到底是进程的通信,在网络中,每个节点有一个网络地址(即IP地址),两个进程通信时,首先要确定各自所在网络节点的网络地址,但是,网络地址只能确定进程所在的计算机,而一台计算机上可能同时有多个网络进程,还不能确定到底是其中的哪个进程,由此套接字中还要有其他的信息,那就是端口号(Port),在一台计算机中,一个端口一次只能分配给一个进程,即端口号与进程是一一对应的关系,所以,端口号和网络地址就能唯一地确定Internet中的一个网络进程。可以认为:套接字=网络地址+端口号系统调用一个Socket()得到一个套接字描述符,然后就可以通过他进行网络通信了。
套接字有很多种类,最常用的就有两种;流式套接字和数据报套接字。在Linux中分别称之为"SOCK_STREAM"和"SOCK_DGRAM)"他们分别使用不同的协议,流式套接字使用TCP协议,数据报套接字使用UDP协议,本文所使用的是流式套接字协议。
网络通信原理在文件传输程序设计中的应用网络上的绝大多数通信采用的都是客户机/服务器机制(Client/Server),即服务器提供服务,客户是这些服务的使用者,服务器首先创建一个Socket,然后将该Socket与本地地址/端口号绑定(Bind()),成功之后就在相应的Socket上监听(Listen()) 。当Accept()函数捕捉到一个连接服务(Connect())请求时,接受并生成一个新的Socket,并通过这个新的Socket与客户端通信,客户端同样也要创建一个Socket,将该Socket与本地地址/端口号绑定,还需要指定服务器端的地址与端口号,随后向服务器端发出Connect(),请求被服务器端接受后,可以通过Socket与服务器端通信。
TCP是一种面向连接的、可靠的、双向的通信数据流,说他可靠,是因为他使用3段握手协议传输数据,并且在传输时采用"重传肯定确认"机制保证数据的正确发送:接收端收到的数据后要发出一个肯定确认,而发送端必须要能接受到这个肯定信号,否则就要将数据重发。在此原理基础之上,设计了基于Linux操作系统下TCP/IP编程实现文件传输的实例。我们采用客户机/服务器模式通信时,通信双方发送/接收数据的工作流程如图3所示。
文件传输就是基于客户机/服务器模型而设计的,客户机和服务器之间利用TCP建立连续,因文件传输是一个交互式会话系统,客户机每次执行文件传输,都需要与服务器建立控制连接和数据连接,其中控制连接负责传输控制信息、利用控制命令、客户机可以向服务器提出无限次的请求,客户机每次提出的请求,服务器与客户机建立一个数据连接,进行实际的数据传输,数据传输完毕后,对应的数据连接被清除,控制连接依然保持,等待客户机发出新的传输请求,直到客户机撤销控制连接,结束会话。
当进行文件传输时,首先向服务器发出连接请求,服务器验证身份后,与客户端建立连接,双方进入会话状态,这时只要客户端向服务器端发出数据连接请求,建立起数据连接后,双方就进入数据传输状态,数据传输完毕后,数据连接被撤销,如此循环反复,直到会话结束,从而实现将文件从服务器端传输至客户机端。
文件传输程序设计流程[5,客户端的TCP应用程序流程(1)先用Socket()创建本地套接口,给服务器端套接口地址结构赋值。
(2)用Connect()函数使本地套接口向服务器端套接口发出建立连接请求,经3次握手建立TCP连接。
(3)用Read()函数读取所要接收的文件名以及存放在内存里的文件内容。
(4)用Open()函数打开客户端新建立的目标文件,如果没有建立,该函数会自动生成目标文件,等待存放文件内容。
(5)最后用Write()函数将读取的文件内容存放在新的目标文件中,以实现服务器端向客户端的文件传输。
(6)通信结束,用Close()关闭套接口,停止接收文件。
服务器端的TCP应用程序流程(1)先用Open()函数打开等待传输的可读文件;(2)用Socket()创建套接口,并给套接口地址结构赋值;(3)用Bind()函数绑定套接口;(4)用Listen()函数在该套接口上监听请求;(5)用Accept()函数接受请求,产生新的套接口及描述字,并与客户端连接;(6)用Lseek()函数是为了在每次接受客户机连接时,将用于读的源文件指针移到文件头;(7)用Read()函数读取一定长度的源文件数据;(8)最后用Write()函数将读取的源文件数据存放在内存中,以便客户端读取;(9)传输完毕时,用Close()关闭所有进程,结束文件传输。
结语Linux操作系统在网络应用方面具有很强的开发潜力,同时Linux也是可靠性、安全性非常高的系统,因此在基于TCP/IP网络通信的研究与开发中,通常选用Linux操作系统作为开发平台
Ⅵ linux 中tcp套接字编程实例中出现Bind() error :Address already in use 怎样解决
换一个端口,或者把正在占用这个端口的程序干掉。
Ⅶ linux 中TCP套接字编程实例 显示Bind() error:address already in use 怎么办 用netstat -nat 查看后结果
你所使用的套接字已被占用,在Bind()之前你是否申请了套接字,或者申请之后已经被占用,Bind所使用的套接字来源很重要,可以查查。再有就是linux里面你的程序如果非法退出或者没有使用close释放套接字,在程序结束以后系统会延时自动释放套接字资源,但是要等几分钟,之后你就可以重新使用了。
Ⅷ linux 下套接字socket编程,求代码,实在写不出来。总出现各种各样的错误,
linux 编程一般都不会直接空敲代码的,比较熟练的程序员也要经常查man page的。所以,要有信心,有恒心。从基础做起,多看书,多练习。
你说的程序其实挺简单的,写一个,比较ugly,仅给楼主练习作参考。
tcpclient.c
#include "debug.h"
int main (int argc, char *argv[])
{
int cfd;
char buf[BUFSIZE];
struct sockaddr_in addr;
int addrlen = sizeof (addr);
int data[DATALEN] = { 0 };
int recv_data[DATALEN] = { 0 };
int i, j, n, average = 0;
if (argc < 2)
{
printf ("usage : %s server_ip server_port", argv[0]);
exit (0);
}
cfd = socket (PF_INET, SOCK_STREAM, 0);
check_err ("socket", cfd);
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr (argv[1]);
addr.sin_port = htons (atoi (argv[2]));
connect (cfd, (struct sockaddr *)&addr, addrlen);
check_err ("socket", cfd);
for (i = 0; i < DATALEN; i++)
data[i] = i + 50;
for (i = 0; i < DATALEN; i++)
{
sprintf (buf, "%d", data[i]);
check_err ("write", write (cfd, buf, sizeof (buf)));
}
i = j = 0;
for (i = 0; i < DATALEN && read (cfd, buf, sizeof (buf)) > 0; i++)
{
if ((n = atoi (buf)) > 100)
{
recv_data[j++] = n;
printf ("recv data : %d\n", recv_data[i]);
average += n;
}
else
break;
}
average /= j;
printf ("\naverage = %d\n", average);
close (cfd);
return 0;
}
tcpserver.c
#include "debug.h"
int main (int argc, char *argv[])
{
int ret;
int sfd;
int cfd;
char buf[BUFSIZE];
struct sockaddr_in addr;
int len = sizeof (addr);
int recv_data[DATALEN] = {0};
int i, j, n;
if (argc < 2)
{
printf ("usage : %s port\n", argv[0]);
exit (-1);
}
sfd = socket (PF_INET, SOCK_STREAM, 0);
check_err ("socket", errno);
addr.sin_family = AF_INET;
addr.sin_port = htons (atoi (argv[1]));
addr.sin_addr.s_addr = INADDR_ANY;
bind (sfd, (struct sockaddr *)&addr, len);
check_err ("bind", errno);
listen (sfd, BACKLOG);
check_err ("listen", errno);
i = j = n = 0;
if ((cfd = accept (sfd, (struct sockaddr *)&addr, &len)) > 0)
{
for (i = 0; i < DATALEN && read (cfd, buf, BUFSIZE) > 0; i++)
{
if ((n = atoi (buf)) > 100)
recv_data[j++] = n;
}
}
for (i = 0; i < j; i++)
{
sprintf (buf, "%d", recv_data[i]);
check_err ("write", write (cfd, buf, sizeof(buf)));
}
check_err ("write", write (cfd, "0", sizeof(buf)));
close (cfd);
return 0;
}
debug.h
#ifndef _DEBUG_H
#define _DEBUG_H
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#define BACKLOG 10 // for max listen fds
#define DATALEN 100
#define BUFSIZE 16
#define check_err(name, ret) \
if (ret < 0) \
{ \
perror (name); \
exit (-1); \
}
#define check_null(name, ret) \
if (if ret == NULL) \
{ \
perror (name); \
exit (-1); \
}
#endif
编译后,先运行server端,然后运行client端,可以看到server发回的数据,及平均值。
网络知道不能贴代码,排版很差,将就可以看吧,有问题追问。
Ⅸ Linux编程问题,有关本地域套接字和EPOLL的
我写的一个可运行的本地域socket的例子,监听端的服务地址为绝对路径。例如/tmp/ssss.socket
void *lfs_dispatcher_thread_fn (void *arg)
{
struct sockaddr_in clientaddr;
int fdmax;
int newfd;
char buf[1024];
int nbytes;
int addrlen;
int ret;
int epfd = -1;
int res = -1;
struct epoll_event ev;
int index = 0;
int listen_fd, client_fd = -1;
struct sockaddr_un srv_addr;
listen_fd = socket (AF_UNIX, SOCK_STREAM, 0);
if (listen_fd < 0)
{
perror ("cannot create listening socket");
}
else
{
srv_addr.sun_family = AF_UNIX;
strncpy (srv_addr.sun_path, UNIX_DOMAIN,
sizeof (srv_addr.sun_path) - 1);
unlink (UNIX_DOMAIN);
ret =
bind (listen_fd, (struct sockaddr *) &srv_addr,
sizeof (srv_addr));
if (ret == -1)
{
lfs_printf ("cannot bind server socket");
lfs_printf ("srv_addr:%p", &srv_addr);
close (listen_fd);
unlink (UNIX_DOMAIN);
exit (1);
}
}
ret = listen (listen_fd, 1);
if (ret == -1)
{
perror ("cannot listen the client connect request");
close (listen_fd);
unlink (UNIX_DOMAIN);
exit (1);
}
chmod (UNIX_DOMAIN, 00777); //设置通信文件权限
fdmax = listen_fd; /* so far, it's this one */
events = calloc (MAX_CON, sizeof (struct epoll_event));
if ((epfd = epoll_create (MAX_CON)) == -1)
{
perror ("epoll_create");
exit (1);
}
ev.events = EPOLLIN;
ev.data.fd = fdmax;
if (epoll_ctl (epfd, EPOLL_CTL_ADD, fdmax, &ev) < 0)
{
perror ("epoll_ctl");
exit (1);
}
//time(&start);
for (;;)
{
res = epoll_wait (epfd, events, MAX_CON, -1);
client_fd = events[index].data.fd;
for (index = 0; index < MAX_CON; index++)
{
if (client_fd == listen_fd)
{
addrlen = sizeof (clientaddr);
if ((newfd =
accept (listen_fd,
(struct sockaddr *) &clientaddr,
(socklen_t *) & addrlen)) == -1)
{
perror ("Server-accept() error lol!");
}
else
{
// lfs_printf("Server-accept() is OK...\n");
ev.events = EPOLLIN;
ev.data.fd = newfd;
if (epoll_ctl
(epfd, EPOLL_CTL_ADD, newfd, &ev) < 0)
{
perror ("epoll_ctl");
exit (1);
}
}
break;
}
else
{
if (events[index].events & EPOLLHUP)
{
// lfs_printf ("find event");
if (epoll_ctl
(epfd, EPOLL_CTL_DEL, client_fd, &ev) < 0)
{
perror ("epoll_ctl");
}
close (client_fd);
break;
}
if (events[index].events & EPOLLIN)
{
/* going to recv data
*/
if ((nbytes =
recv (client_fd, buf, 1024, 0)) <= 0)
{
if (nbytes == 0)
{
}
else
{
lfs_printf ("recv() error lol! %d",
client_fd);
perror ("");
}
if (epoll_ctl
(epfd, EPOLL_CTL_DEL, client_fd,
&ev) < 0)
{
perror ("epoll_ctl");
}
close (client_fd);
}
else
{
// lfs_printf ("nbytes=%d,recv %s,%c", nbytes,
// buf, buf[6]);
process_request (buf, client_fd);
memset (buf, 0, 4);
}
break;
}
}
}
}
return 0;
}
Ⅹ 求前辈指教。linux的套接字编程,这个程序运行,我照着源码敲,出现一堆错误。用的是vim和gcc
再启动时会出现:
Bind(): Address already in use
的错误提示,并导致程序直接退出;
用
$netstat -an |grep 8080
或
$ps aux |grep 8080
都还能看到刚才用Ctrl+C“强制结束”了的进程,端口还是使用中,
只能用kill结束进程,才能收回端口,很是麻烦。
在代码中添加:
int optval;
optval = 1;
ret = setsockopt( sock, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval) );
可以解决这问题。
在网上查到的更好的解释如下:
http://www.ibm.com/developerworks/cn/linux/l-sockpit/
在 4.2 BSD UNIX® 操作系统中首次引入,Sockets API 现在是任何操作系统的标准特性。
事实上,很难找到一种不支持 Sockets API 的现代语言。
该 API 相当简单,但新的开发人员仍然会遇到一些常见的隐患。
本文识别那些隐患并向您显示如何避开它们。
隐患 1.忽略返回状态
第一个隐患很明显,但它是开发新手最容易犯的一个错误。
如果您忽略函数的返回状态,当它们失败或部分成功的时候,您也许会迷失。
反过来,这可能传播错误,使定位问题的源头变得困难。
捕获并检查每一个返回状态,而不是忽略它们。
考虑清单 1 显示的例子,一个套接字 send 函数。
清单 1. 忽略 API 函数返回状态
int status, sock, mode;
/* Create a new stream (TCP) socket */
sock = socket( AF_INET, SOCK_STREAM, 0 );
...
status = send( sock, buffer, buflen, MSG_DONTWAIT );
if (status == -1)
{
/* send failed */
printf( "send failed: %s\n", strerror(errno) );
}
else
{
/* send succeeded -- or did it? */
}
清单 1 探究一个函数片断,它完成套接字 send 操作(通过套接字发送数据)。
函数的错误状态被捕获并测试,但这个例子忽略了send 在无阻塞模式(由 MSG_DONTWAIT 标志启用)下的一个特性。
send API 函数有三类可能的返回值:
如果数据成功地排到传输队列,则返回 0。
如果排队失败,则返回 -1(通过使用 errno 变量可以了解失败的原因)。
如果不是所有的字符都能够在函数调用时排队,则最终的返回值是发送的字符数。
由于 send 的 MSG_DONTWAIT 变量的无阻塞性质,
函数调用在发送完所有的数据、一些数据或没有发送任何数据后返回。
在这里忽略返回状态将导致不完全的发送和随后的数据丢失。
隐患 2.对等套接字闭包
UNIX 有趣的一面是您几乎可以把任何东西看成是一个文件。
文件本身、目录、管道、设备和套接字都被当作文件。
这是新颖的抽象,意味着一整套的 API 可以用在广泛的设备类型上。
考虑 read API 函数,它从文件读取一定数量的字节。
read 函数返回:
读取的字节数(最高为您指定的最大值);
或者 -1,表示错误;
或者 0,如果已经到达文件末尾。
如果在一个套接字上完成一个 read 操作并得到一个为 0 的返回值,这表明远程套接字端的对等层调用了 close API 方法。
该指示与文件读取相同 —— 没有多余的数据可以通过描述符读取(参见 清单 2)。
清单 2.适当处理 read API 函数的返回值
int sock, status;
sock = socket( AF_INET, SOCK_STREAM, 0 );
...
status = read( sock, buffer, buflen );
if (status > 0)
{
/* Data read from the socket */
}
else if (status == -1)
{
/* Error, check errno, take action... */
}
else if (status == 0)
{
/* Peer closed the socket, finish the close */
close( sock );
/* Further processing... */
}
同样,可以用 write API 函数来探测对等套接字的闭包。
在这种情况下,接收 SIGPIPE 信号,或如果该信号阻塞,write 函数将返回 -1 并设置 errno 为 EPIPE。
隐患 3.地址使用错误(EADDRINUSE)
您可以使用 bind API 函数来绑定一个地址(一个接口和一个端口)到一个套接字端点。
可以在服务器设置中使用这个函数,以便限制可能有连接到来的接口。
也可以在客户端设置中使用这个函数,以便限制应当供出去的连接所使用的接口。
bind 最常见的用法是关联端口号和服务器,并使用通配符地址(INADDR_ANY),它允许任何接口为到来的连接所使用。
bind 普遍遭遇的问题是试图绑定一个已经在使用的端口。
该陷阱是也许没有活动的套接字存在,但仍然禁止绑定端口(bind 返回EADDRINUSE),
它由 TCP 套接字状态 TIME_WAIT 引起。
该状态在套接字关闭后约保留 2 到 4 分钟。
在 TIME_WAIT 状态退出之后,套接字被删除,该地址才能被重新绑定而不出问题。
等待 TIME_WAIT 结束可能是令人恼火的一件事,
特别是如果您正在开发一个套接字服务器,就需要停止服务器来做一些改动,然后重启。
幸运的是,有方法可以避开 TIME_WAIT 状态。可以给套接字应用 SO_REUSEADDR 套接字选项,以便端口可以马上重用。
考虑清单 3 的例子。
在绑定地址之前,我以 SO_REUSEADDR 选项调用 setsockopt。
为了允许地址重用,我设置整型参数(on)为 1 (不然,可以设为 0 来禁止地址重用)。
清单 3.使用 SO_REUSEADDR 套接字选项避免地址使用错误
int sock, ret, on; struct sockaddr_in servaddr; /* Create a new stream (TCP) socket */ sock = socket( AF_INET, SOCK_STREAM, 0 ):
/* Enable address reuse */
on = 1;
ret = setsockopt( sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on) );
/* Allow connections to port 8080 from any available interface */
memset( &servaddr, 0, sizeof(servaddr) );
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl( INADDR_ANY );
servaddr.sin_port = htons( 45000 );
/* Bind to the address (interface/port) */
ret = bind( sock, (struct sockaddr *)&servaddr, sizeof(servaddr) );
在应用了 SO_REUSEADDR 选项之后,bind API 函数将允许地址的立即重用。
隐患 4.发送结构化数据
套接字是发送无结构二进制字节流或 ASCII 数据流(比如 HTTP 上的 HTTP 页面,或 SMTP 上的电子邮件)的完美工具。但是如果试图在一个套接字上发送二进制数据,事情将会变得更加复杂。
比如说,您想要发送一个整数:您可以肯定,接收者将使用同样的方式来解释该整数吗?
运行在同一架构上的应用程序可以依赖它们共同的平台来对该类型的数据做出相同的解释。
但是,如果一个运行在高位优先的 IBM PowerPC 上的客户端发送一个 32 位的整数到一个低位优先的 Intel x86,
那将会发生什么呢?
字节排列将引起不正确的解释。
字节交换还是不呢?
Endianness 是指内存中字节的排列顺序。高位优先(big endian) 按最高有效字节在前排列,然而 低位优先(little endian) 按照最低有效字节在前排序。
高位优先架构(比如 PowerPC®)比低位优先架构(比如 Intel® Pentium® 系列,其网络字节顺序是高位优先)有优势。这意味着,对高位优先的机器来说,在 TCP/IP 内控制数据是自然有序的。低位优先架构要求字节交换 —— 对网络应用程序来说,这是一个轻微的性能弱点。
通过套接字发送一个 C 结构会怎么样呢?这里,也会遇到麻烦,因为不是所有的编译器都以相同的方式排列一个结构的元素。结构也可能被压缩以便使浪费的空间最少,这进一步使结构中的元素错位。
幸好,有解决这个问题的方案,能够保证两端数据的一致解释。过去,远程过程调用(Remote Procere Call,RPC)套装工具提供所谓的外部数据表示(External Data Representation,XDR)。XDR 为数据定义一个标准的表示来支持异构网络应用程序通信的开发。
现在,有两个新的协议提供相似的功能。可扩展标记语言/远程过程调用(XML/RPC)以 XML 格式安排 HTTP 上的过程调用。数据和元数据用 XML 进行编码并作为字符串传输,并通过主机架构把值和它们的物理表示分开。SOAP 跟随 XML-RPC,以更好的特性和功能扩展了它的思想。参见 参考资料 小节,获取更多关于每个协议的信息。
回页首
隐患 5.TCP 中的帧同步假定
TCP 不提供帧同步,这使得它对于面向字节流的协议是完美的。
这是 TCP 与 UDP(User Datagram Protocol,用户数据报协议)的一个重要区别。
UDP 是面向消息的协议,它保留发送者和接收者之间的消息边界。
TCP 是一个面向流的协议,它假定正在通信的数据是无结构的,
如图 1 所示。
图 1.UDP 的帧同步能力和缺乏帧同步的 TCP
图 1 的上部说明一个 UDP 客户端和服务器。
左边的对等层完成两个套接字的写操作,每个 100 字节。
协议栈的 UDP 层追踪写的数量,并确保当右边的接收者通过套接字获取数据时,它以同样数量的字节到达。
换句话说,为读者保留了写者提供的消息边界。
现在,看图 1 的底部.它为 TCP 层演示了相同粒度的写操作。
两个独立的写操作(每个 100 字节)写入流套接字。
但在本例中,流套接字的读者得到的是 200 字节。
协议栈的 TCP 层聚合了两次写操作。
这种聚合可以发生在 TCP/IP 协议栈的发送者或接收者中任何一方。
重要的是,要注意到聚合也许不会发生 —— TCP 只保证数据的有序发送。
对大多数开发人员来说,该陷阱会引起困惑。
您想要获得 TCP 的可靠性和 UDP 的帧同步。
除非改用其他的传输协议,比如流传输控制协议(STCP),
否则就要求应用层开发人员来实现缓冲和分段功能。
调试套接字应用程序的工具
GNU/Linux 提供几个工具,它们可以帮助您发现套接字应用程序中的一些问题。
此外,使用这些工具还有教育意义,而且能够帮助解释应用程序和 TCP/IP 协议栈的行为。
在这里,您将看到对几个工具的概述。查阅下面的 参考资料 了解更多的信息。
查看网络子系统的细节
netstat 工具提供查看 GNU/Linux 网络子系统的能力。
使用 netstat,可以查看当前活动的连接(按单个协议进行查看),
查看特定状态的连接(比如处于监听状态的服务器套接字)和许多其他的信息。
清单 4 显示了 netstat 提供的一些选项和它们启用的特性。
清单 4.netstat 实用程序的用法模式
View all TCP sockets currently active $ netstat --tcp View all UDP sockets $ netstat --udp View all TCP sockets in the listening state $ netstat --listening View the multicast group membership information $ netstat --groups Display the list of masqueraded connections $ netstat --masquerade View statistics for each protocol $ netstat --statistics
尽管存在许多其他的实用程序,但 netstat 的功能很全面,
它覆盖了 route、ifconfig 和其他标准 GNU/Linux 工具的功能。
监视流量
可以使用 GNU/Linux 的几个工具来检查网络上的低层流量。
tcpmp 工具是一个比较老的工具,它从网上“嗅探”网络数据包,打印到stdout 或记录在一个文件中。
该功能允许查看应用程序产生的流量和 TCP 生成的低层流控制机制。
一个叫做 tcpflow 的新工具与tcpmp 相辅相成,
它提供协议流分析和适当地重构数据流的方法,而不管数据包的顺序或重发。
清单 5 显示 tcpmp 的两个用法模式。
清单 5.tcpmp 工具的用法模式
Display all traffic on the eth0 interface for the local host
$ tcpmp -l -i eth0 // Show all traffic on the network coming from or going to host plato
$ tcpmp host plato // Show all HTTP traffic for host camus
$ tcpmp host camus and (port http) //View traffic coming from or going to TCP port 45000 on the local host
$ tcpmp tcp port 45000
tcpmp 和 tcpflow 工具有大量的选项,包括创建复杂过滤表达式的能力。
查阅下面的 参考资料 获取更多关于这些工具的信息。
tcpmp 和 tcpflow 都是基于文本的命令行工具。
如果您更喜欢图形用户界面(GUI),有一个开放源码工具 Ethereal 也许适合您的需要。
Ethereal 是一个专业的协议分析软件,它可以帮助调试应用层协议。
它的插入式架构(plug-in architecture)可以分解协议,
比如 HTTP 和您能想到的任何协议(写本文的时候共有 637 个协议)。
回页首
总结
套接字编程是容易而有趣的,但是您要避免引入错误或至少使它们容易被发现,
这就需要考虑本文中描述的这 5 个常见的陷阱,并且采用标准的防错性程序设计实践。
GNU/Linux 工具和实用程序还可以帮助发现一些程序中的小问题。
记住:在查看实用程序的帮助手册时候,跟踪相关的或“请参见”工具。
您也许会发现一个必要的新工具。