linux下socket编程原理(一)
UNIX系统的I/O命令集,是从Maltics和早期系统中的命令演变出来的,其模式为打开一读/写一关闭(open-write-read-close)。在一个用户进程进行I/O操作时,它首先调用"打开"获得对指定文件或设备的使用权,并返回称为文件描述符的整型数,以描述用户在打开的文件或设备上进行I/O操作的进程。然后这个用户进程多次调用"读/写"以传输数据。当所有的传输操作完成后,用户进程关闭调用,通知操作系统已经完成了对某对象的使用。
TCP/IP协议被集成到UNIX内核中时,相当于在UNIX系统引入了一种新型的I/O操作。UNIX用户进程与网络协议的交互作用比用户进程与传统的I/O设备相互作用复杂得多。首先,进行网络操作的两个进程在不同机器上,如何建立它们之间的联系?其次,网络协议存在多种,如何建立一种通用机制以支持多种协议?这些都是网络应用编程界面所要解决的问题。
在UNIX系统中,网络应用编程界面有两类:UNIX BSD的套接字(socket)和UNIX System V的TLI。由于Sun公司采用了支持TCP/IP的UNIX BSD操作系统,使TCP/IP的应用有更大的发展,其网络应用编程界面──套接字(socket)在网络软件中被广泛应用,至今已引进微机操作系统DOS和Windows系统中,成为开发网络应用软件的强有力工具,本章将要详细讨论这个问题。
2.2 套接字编程基本概念
在开始使用套接字编程之前,首先必须建立以下概念。
2.2.1 网间进程通信
进程通信的概念最初来源于单机系统。由于每个进程都在自己的地址范围内运行,为保证两个相互通信的进程之间既互不干扰又协调一致工作,操作系统为进程通信提供了相应设施,如UNIX BSD中的管道(pipe)、命名管道(named pipe)和软中断信号(signal),UNIX system V的消息(message)、共享存储区(shared memory)和信号量(semaphore)等,但都仅限于用在本机进程之间通信。网间进程通信要解决的是不同主机进程间的相互通信问题(可把同机进程通信看作是其中的特例)。为此,首先要解决的是网间进程标识问题。同一主机上,不同进程可用进程号(process ID)唯一标识。但在网络环境下,各主机独立分配的进程号不能唯一标识该进程。例如,主机A赋于某进程号5,在B机中也可以存在5号进程,因此,"5号进程"这句话就没有意义了。
其次,操作系统支持的网络协议众多,不同协议的工作方式不同,地址格式也不同。因此,网间进程通信还要解决多重协议的识别问题。
为了解决上述问题,TCP/IP协议引入了下列几个概念。
端口
网络中可以被命名和寻址的通信端口,是操作系统可分配的一种资源。
按照OSI七层协议的描述,传输层与网络层在功能上的最大区别是传输层提供进程通信能力。从这个意义上讲,网络通信的最终地址就不仅仅是主机地址了,还包括可以描述进程的某种标识符。为此,TCP/IP协议提出了协议端口(protocol port,简称端口)的概念,用于标识通信的进程。
端口是一种抽象的软件结构(包括一些数据结构和I/O缓冲区)。应用程序(即进程)通过系统调用与某端口建立连接(binding)后,传输层传给该端口的数据都被相应进程所接收,相应进程发给传输层的数据都通过该端口输出。在TCP/IP协议的实现中,端口操作类似于一般的I/O操作,进程获取一个端口,相当于获取本地唯一的I/O文件,可以用一般的读写原语访问之。
类似于文件描述符,每个端口都拥有一个叫端口号(port number)的整数型标识符,用于区别不同端口。由于TCP/IP传输层的两个协议TCP和UDP是完全独立的两个软件模块,因此各自的端口号也相互独立,如TCP有一个255号端口,UDP也可以有一个255号端口,二者并不冲突。
端口号的分配是一个重要问题。有两种基本分配方式:第一种叫全局分配,这是一种集中控制方式,由一个公认的中央机构根据用户需要进行统一分配,并将结果公布于众。第二种是本地分配,又称动态连接,即进程需要访问传输层服务时,向本地操作系统提出申请,操作系统返回一个本地唯一的端口号,进程再通过合适的系统调用将自己与该端口号联系起来(绑扎)。TCP/IP端口号的分配中综合了上述两种方式。TCP/IP将端口号分为两部分,少量的作为保留端口,以全局方式分配给服务进程。因此,每一个标准服务器都拥有一个全局公认的端口(即周知口,well-known port),即使在不同机器上,其端口号也相同。剩余的为自由端口,以本地方式进行分配。TCP和UDP均规定,小于256的端口号才能作保留端口。
地址
网络通信中通信的两个进程分别在不同的机器上。在互连网络中,两台机器可能位于不同的网络,这些网络通过网络互连设备(网关,网桥,路由器等)连接。因此需要三级寻址:
面向连接
可靠的报文流
可靠的字节流
不可靠的连接
文件传输(FTP)
远程登录(Telnet)
数字话音
无连接
不可靠的数据报
有确认的数据报
请求-应答
电子邮件(E-mail)
电子邮件中的挂号信
网络数据库查询
顺序
在网络传输中,两个连续报文在端-端通信中可能经过不同路径,这样到达目的地时的顺序可能会与发送时不同。“顺序"是指接收数据顺序与发送数据顺序相同。TCP协议提供这项服务。
差错控制
保证应用程序接收的数据无差错的一种机制。检查差错的方法一般是采用检验"检查和(Checksum)“的方法。而保证传送无差错的方法是双方采用确认应答技术。TCP协议提供这项服务。
流控制
在数据传输过程中控制数据传输速率的一种机制,以保证数据不被丢失。TCP协议提供这项服务。
字节流
字节流方式指的是仅把传输中的报文看作是一个字节序列,不提供数据流的任何边界。TCP协议提供字节流服务。
报文
接收方要保存发送方的报文边界。UDP协议提供报文服务。
全双工/半双工
端-端间数据同时以两个方向/一个方向传送。
缓存/带外数据
在字节流服务中,由于没有报文边界,用户进程在某一时刻可以读或写任意数量的字节。为保证传输正确或采用有流控制的协议时,都要进行缓存。但对某些特殊的需求,如交互式应用程序,又会要求取消这种缓存。
在数据传送过程中,希望不通过常规传输方式传送给用户以便及时处理的某一类信息,如UNIX系统的中断键(Delete或Control-c)、终端流控制符(Control-s和Control-q),称为带外数据。逻辑上看,好象用户进程使用了一个独立的通道传输这些数据。该通道与每对连接的流相联系。由于Berkeley Software Distribution中对带外数据的实现与RFC 1122中规定的Host Agreement不一致,为了将互操作中的问题减到最小,应用程序编写者除非与现有服务互操作时要求带外数据外,最好不使用它。
2.2.3 客户/服务器模式
在TCP/IP网络应用中,通信的两个进程间相互作用的主要模式是客户/服务器模式(Client/Server model),即客户向服务器发出服务请求,服务器接收到请求后,提供相应的服务。客户/服务器模式的建立基于以下两点:首先,建立网络的起因是网络中软硬件资源、运算能力和信息不均等,需要共享,从而造就拥有众多资源的主机提供服务,资源较少的客户请求服务这一非对等作用。其次,网间进程通信完全是异步的,相互通信的进程间既不存在父子关系,又不共享内存缓冲区,因此需要一种机制为希望通信的进程间建立联系,为二者的数据交换提供同步,这就是基于客户/服务器模式的TCP/IP。
客户/服务器模式在操作过程中采取的是主动请求方式:
首先服务器方要先启动,并根据请求提供相应服务:
图2.1 面向连接的套接字系统调用时序图
无连接协议的套接字调用如图2.2所示:
图2.2 无连接协议的套接字调用时序图
无连接服务器也必须先启动,否则客户请求传不到服务进程。无连接客户不调用connect()。因此在数据发送之前,客户与服务器之间尚未建立完全相关,但各自通过socket()和bind()建立了半相关。发送数据时,发送方除指定本地套接字号外,还需指定接收方套接字号,从而在数据收发过程中动态地建立了全相关。
实例
本实例使用面向连接协议的客户/服务器模式,其流程如图2.3所示:
图2.3 面向连接的应用程序流程图
服务器方程序:
/ 这个程序建立一个套接字,然后开始无限循环;每当它通过循环接收到一个连接,则打印出一个信息。当连接断开,或接收到终止信息,则此连接结束,程序再接收一个新的连接。命令行的格式是:streams /
main( )
{
int sock, length;
struct sockaddr_in server;
struct sockaddr tcpaddr;
int msgsock;
char buf[1024];
int rval, len;
/ 建立套接字 /
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
perror(“opening stream socket”);
exit(1);
}
/ 使用任意端口命名套接字 /
server.sin_family = AF_INET;
server.sin_port = INADDR_ANY;
if (bind(sock, (struct sockaddr )&server, sizeof(server)) < 0) {
perror(“binding stream socket”);
exit(1);
}
/ 找出指定的端口号并打印出来 /
length = sizeof(server);
if (getsockname(sock, (struct sockaddr )&server, &length) < 0) {
perror(“getting socket name”);
exit(1);
}
printf(“socket port #%d\n”, ntohs(server.sin_port));
/ 开始接收连接 /
listen(sock, 5);
len = sizeof(struct sockaddr);
do {
msgsock = accept(sock, (struct sockaddr )&tcpaddr, (int )&len);
if (msgsock == -1)
perror(“accept”);
else do{
memset(buf, 0, sizeof(buf));
if ((rval = recv(msgsock, buf, 1024)) < 0)
perror(“reading stream message”);
if (rval == 0)
printf(“ending connection \n”);
else
printf(“–>%s\n”, buf);
}while (rval != 0);
closesocket(msgsock);
} while (TRUE);
/ 因为这个程序已经有了一个无限循环,所以套接字"sock"从来不显式关闭。然而,当进程被杀死或正常终止时,所有套接字都将自动地被关闭。/
exit(0);
}
客户方程序:
/ 这个程序建立套接字,然后与命令行给出的套接字连接;连接结束时,在连接上发送
一个消息,然后关闭套接字。命令行的格式是:streamc 主机名 端口号
端口号要与服务器程序的端口号相同 /
main(argc, argv)
int argc;
char argv[ ];
{
int sock;
struct sockaddr_in server;
struct hostent hp, gethostbyname( );
char buf[1024];
/ 建立套接字 /
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
perror(“opening stream socket”);
exit(1);
}
/ 使用命令行中指定的名字连接套接字 /
server.sin_family = AF_INET;
hp = gethostbyname(argv[1]);
if (hp == 0) {
fprintf(stderr, “%s: unknown host \n”, argv[1]);
exit(2);
}
memcpy((char)&server.sin_addr, (char)hp->h_addr, hp->h_length);
sever.sin_port = htons(atoi(argv[2]));
if (connect(sock, (struct sockaddr)&server, sizeof(server)) < 0) {
perror(“connecting stream socket”);
exit(3);
}
if (send(sock, DATA, sizeof(DATA)) < 0)
perror(“sending on stream socket”
linux下socket编程原理(六)
);
closesocket(sock);
exit(0);
}
2.5 一个通用的实例程序
在上一节中,我们介绍了一个简单的socket程序实例。从这个例子我们可以看出,使用socket编程几乎有一个模式,即所有的程序几乎毫无例外地按相同的顺序调用相同的函数。因此我们可以设想,设计一个中间层,它向上提供几个简单的函数,程序只要调用这几个函数就可以实现普通情况下的数据传输,程序设计者不必太多地关心socket程序设计的细节。
本节我们将介绍一个通用的网络程序接口,它向上层提供几个简单的函数,程序设计者只要使用这几个函数就可以完成绝大多数情况下的网络数据传输。这些函数将socket编程和上层隔离开来,它使用面向连接的流式套接字,采用非阻塞的工作机制,程序只要调用这些函数查询网络消息并作出相应的响应即可。这些函数包括:
l InitSocketsStruct:初始化socket结构,获取服务端口号。客户程序使用。
l InitPassiveSock:初始化socket结构,获取服务端口号,建立主套接字。服务器程序使用。
l CloseMainSock:关闭主套接字。服务器程序使用。
l CreateConnection:建立连接。客户程序使用。
l AcceptConnection:接收连接。服务器程序使用。
l CloseConnection:关闭连接。
l QuerySocketsMsg:查询套接字消息。
l SendPacket:发送数据。
l RecvPacket:接收数据。
2.5.1 头文件
/ File Name: tcpsock.h /
/ 头文件包括socket程序经常用到的系统头文件(本例中给出的是SCO Unix下的头文件,其它版本的Unix的头文件可能略有不同),并定义了我们自己的两个数据结构及其实例变量,以及我们提供的函数说明。/
typedef struct SocketsMsg{ / 套接字消息结构 /
int AcceptNum; / 指示是否有外来连接等待接收 /
int ReadNum; / 有外来数据等待读取的连接数 /
int ReadQueue[32]; / 有外来数据等待读取的连接队列 /
int WriteNum; / 可以发送数据的连接数 /
int WriteQueue[32]; / 可以发送数据的连接队列 /
int ExceptNum; / 有例外的连接数 /
int ExceptQueue[32]; / 有例外的连接队列 /
} SocketsMsg;
typedef struct Sockets { / 套接字结构 /
int DaemonSock; / 主套接字 /
int SockNum; / 数据套接字数目 /
int Sockets[64]; / 数据套接字数组 /
fd_set readfds, writefds, exceptfds; / 要被检测的可读、可写、例外的套接字集合 /
int Port; / 端口号 /
} Sockets;
Sockets Mysock; / 全局变量 /
SocketsMsg SockMsg;
int InitSocketsStruct(char * servicename) ;
int InitPassiveSock(char * servicename) ;
void CloseMainSock();
int CreateConnection(struct in_addr sin_addr);
int AcceptConnection(struct in_addr IPaddr);
int CloseConnection(int Sockno);
int QuerySocketsMsg();
int SendPacket(int Sockno, void buf, int len);
int RecvPacket(int Sockno, void buf, int size);
2.5.2 函数源文件
/ File Name: tcpsock.c /
/ 本文件给出九个函数的源代码,其中部分地方给出中文注释 /
int InitSocketsStruct(char * servicename)
/ Initialize Sockets structure. If succeed then return 1, else return error code (<0) * /
/ 此函数用于只需要主动套接字的客户程序,它用来获取服务信息。服务的定义
在/etc/services文件中 /
{
struct servent servrec;
struct sockaddr_in serv_addr;
if ((servrec = getservbyname(servicename, “tcp”)) == NULL) {
return(-1);
}
bzero((char )&Mysock, sizeof(Sockets));
Mysock.Port = servrec->s_port; / Service Port in Network Byte Order /
return(1);
}
int InitPassiveSock(char * servicename)
/ Initialize Passive Socket. If succeed then return 1, else return error code (<0) * /
/ 此函数用于需要被动套接字的服务器程序,它除了获取服务信息外,还建立
一个被动套接字。/
{
int mainsock, flag=1;
struct servent servrec;
struc
linux下socket编程原理(七)
t sockaddr_in serv_addr;
if ((servrec = getservbyname(servicename, “tcp”)) == NULL) {
return(-1);
}
bzero((char )&Mysock, sizeof(Sockets));
Mysock.Port = servrec->s_port; / Service Port in Network Byte Order /
if((mainsock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
return(-2);
}
bzero((char )&serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); / 任意网络接口 /
serv_addr.sin_port = servrec->s_port;
if (bind(mainsock, (struct sockaddr)&serv_addr, sizeof(serv_addr)) < 0) {
close(mainsock);
return(-3);
}
if (listen(mainsock, 5) == -1) { / 将主动套接字变为被动套接字,准备好接收连接 /
close(mainsock);
return(-4);
}
/ Set this socket as a Non-blocking socket. /
if (ioctl(mainsock, FIONBIO, &flag) == -1) {
close(mainsock);
return(-5);
}
Mysock.DaemonSock = mainsock;
FD_SET(mainsock, &Mysock.readfds); / 申明对主套接字"可读"感兴趣 /
FD_SET(mainsock, &Mysock.exceptfds); / 申明对主套接字上例外事件感兴趣 /
return(1);
}
void CloseMainSock()
/ 关闭主套接字,并清除对它上面事件的申明。在程序结束前关闭主套接字是一个好习惯 /
{
close(Mysock.DaemonSock);
FD_CLR(Mysock.DaemonSock, &Mysock.readfds);
FD_CLR(Mysock.DaemonSock, &Mysock.exceptfds);
}
int CreateConnection(struct in_addr sin_addr)
/ Create a Connection to remote host which IP address is in sin_addr.
Param: sin_addr indicates the IP address in Network Byte Order.
if succeed return the socket number which indicates this connection,
else return error code (<0) * /
{
struct sockaddr_in server; / server address /
int tmpsock, flag=1, i;
if ((tmpsock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
return(-1);
server.sin_family = AF_INET;
server.sin_port = Mysock.Port;
server.sin_addr.s_addr = sin_addr->s_addr;
/ Set this socket as a Non-blocking socket. /
if (ioctl(tmpsock, FIONBIO, &flag) == -1) {
close(tmpsock);
return(-2);
}
/ Connect to the server. /
if (connect(tmpsock, (struct sockaddr )&server, sizeof(server)) < 0) {
if ((errno != EWOULDBLOCK) && (errno != EINPROGRESS)) {
/ 如果错误代码是EWOULDBLOCK和EINPROGRESS,则不用关闭套接字,因为系统将在之后继续为套接字建立连接,连接是否建立成功可用select()函数来检测套接字是否"可写"来确定。/
close(tmpsock);
return(-3); / Connect error. /
}
}
FD_SET(tmpsock, &Mysock.readfds);
FD_SET(tmpsock, &Mysock.writefds);
FD_SET(tmpsock, &Mysock.exceptfds);
i = 0;
while (Mysock.Sockets != 0) i++; / look for a blank sockets position /
if (i >= 64) {
close(tmpsock);
return(-4); / too many connections /
}
Mysock.Sockets = tmpsock;
Mysock.SockNum++;
return(i);
}
int AcceptConnection(struct in_addr IPaddr)
/ Accept a connection. If succeed, return the data sockets number, else return -1. /
{
int newsock, len, flag=1, i;
struct sockaddr_in addr;
len = sizeof(addr);
bzero((char )&addr, len);
linux下socket编程原理(八)
if ((newsock = accept(Mysock.DaemonSock, &addr, &len)) == -1)
return(-1); / Accept error. /
/ Set this socket as a Non-blocking socket. /
ioctl(newsock, FIONBIO, &flag);
FD_SET(newsock, &Mysock.readfds);
FD_SET(newsock, &Mysock.writefds);
FD_SET(newsock, &Mysock.exceptfds);
/ Return IP address in the Parameter. /
IPaddr->s_addr = addr.sin_addr.s_addr;
i = 0;
while (Mysock.Sockets != 0) i++; / look for a blank sockets position /
if (i >= 64) {
close(newsock);
return(-4); / too many connections /
}
Mysock.Sockets = newsock;
Mysock.SockNum++;
return(i);
}
int CloseConnection(int Sockno)
/ Close a connection indicated by Sockno. /
{
int retcode;
if ((Sockno >= 64) || (Sockno < 0) || (Mysock.Sockets[Sockno] == 0))
return(0);
retcode = close(Mysock.Sockets[Sockno]);
FD_CLR(Mysock.Sockets[Sockno], &Mysock.readfds);
FD_CLR(Mysock.Sockets[Sockno], &Mysock.writefds);
FD_CLR(Mysock.Sockets[Sockno], &Mysock.exceptfds);
Mysock.Sockets[Sockno] = 0;
Mysock.SockNum–;
return(retcode);
}
int QuerySocketsMsg()
/ Query Sockets Message. If succeed return message number, else return -1.
The message information stored in struct SockMsg. /
{
fd_set rfds, wfds, efds;
int retcode, i;
struct timeval TimeOut;
rfds = Mysock.readfds;
wfds = Mysock.writefds;
efds = Mysock.exceptfds;
TimeOut.tv_sec = 0; / 立即返回,不阻塞。/
TimeOut.tv_usec = 0;
bzero((char )&SockMsg, sizeof(SockMsg));
if ((retcode = select(64, &rfds, &wfds, &efds, &TimeOut)) == 0)
return(0);
if (FD_ISSET(Mysock.DaemonSock, &rfds))
SockMsg.AcceptNum = 1; / some client call server. /
for (i=0; i<64; i++) /* Data in message */
{
if ((Mysock.Sockets > 0) && (FD_ISSET(Mysock.Sockets, &rfds)))
SockMsg.ReadQueue[SockMsg.ReadNum++] = i;
}
for (i=0; i<64; i++) /* Data out ready message */
{
if ((Mysock.Sockets > 0) && (FD_ISSET(Mysock.Sockets, &wfds)))
SockMsg.WriteQueue[SockMsg.WriteNum++] = i;
}
if (FD_ISSET(Mysock.DaemonSock, &efds))
SockMsg.AcceptNum = -1; / server socket error. /
for (i=0; i<64; i++) /* Error message */
{
if ((Mysock.Sockets > 0) && (FD_ISSET(Mysock.Sockets, &efds)))
SockMsg.ExceptQueue[SockMsg.ExceptNum++] = i;
}
return(retcode);
}
int SendPacket(int Sockno, void buf, int len)
/ Send a packet. If succeed return the number of send data, else return -1 /
{
int actlen;
if ((Sockno >= 64) || (Sockno < 0) || (Mysock.Sockets[Sockno] == 0))
return(0);
if ((actlen = send(Mysock.Sockets[Sockno], buf, len, 0)) < 0)
return(-1);
return(actlen);
}
int RecvPacket(int Sockno, void buf, int size)
/ Receive a packet. If succeed return the number of receive data, else
linux下socket编程原理(九)
if the connection
is shutdown by peer then return 0, otherwise return 0-errno /
{
int actlen;
if ((Sockno >= 64) || (Sockno < 0) || (Mysock.Sockets[Sockno] == 0))
return(0);
if ((actlen = recv(Mysock.Sockets[Sockno], buf, size, 0)) < 0)
return(0-errno);
return(actlen); / actlen是接收的数据长度,如果为零,指示连接被对方关闭。/
}
2.5.3 简单服务器程序示例
/ File Name: server.c /
/ 这是一个很简单的重复服务器程序,它初始化好被动套接字后,循环等待接收连接。如果接收到连接,它显示数据套接字序号和客户端的IP地址;如果数据套接字上有数据到来,它接收数据并显示该连接的数据套接字序号和接收到的字符串。/
main(argc, argv)
int argc;
char *argv;
{
struct in_addr sin_addr;
int retcode, i;
char buf[32];
/ 对于服务器程序,它经常是处于无限循环状态,只有在用户主动kill该进程或系统关机时,它才结束。对于使用kill强行终止的服务器程序,由于主套接字没有关闭,资源没有主动释放,可能会给随后的服务器程序重新启动产生影响。因此,主动关闭主套接字是一个良好的变成习惯。下面的语句使程序在接收到SIGINT、SIGQUIT和SIGTERM等信号时先执行CloseMainSock()函数关闭主套接字,然后再结束程序。因此,在使用kill强行终止服务器进程时,应该先使用kill -2 PID给服务器程序一个消息使其关闭主套接字,然后在用kill -9 PID强行结束该进程。/
(void) signal(SIGINT, CloseMainSock);
(void) signal(SIGQUIT, CloseMainSock);
(void) signal(SIGTERM, CloseMainSock);
if ((retcode = InitPassiveSock(“TestService”)) < 0) {
printf(“InitPassiveSock: error code = %d\n”, retcode);
exit(-1);
}
while (1) {
retcode = QuerySocketsMsg(); / 查询网络消息 /
if (SockMsg.AcceptNum == 1) { / 有外来连接等待接收?/
retcode = AcceptConnection(&sin_addr);
printf(“retcode = %d, IP = %s \n”, retcode, inet_ntoa(sin_addr.s_addr));
}
else if (SockMsg.AcceptNum == -1) / 主套接字错误?/
printf(“Daemon Sockets error.\n”);
for (i=0; i< * 接收外来数据 { i++)>
if ((retcode = RecvPacket(SockMsg.ReadQueue, buf, 32)) > 0)
printf(“sockno %d Recv string = %s \n”, SockMsg.ReadQueue, buf);
else / 返回数据长度为零,指示连接中断,关闭套接字。/
CloseConnection(SockMsg.ReadQueue);
}
} / end while /
}
2.5.4 简单客户程序示例
/ File Name: client.c /
/ 客户程序在执行时,先初始化数据结构,然后等待用户输入命令。它识别四个命令:
conn(ect): 和服务器建立连接;
send: 给指定连接发送数据;
clos(e): 关闭指定连接;
quit: 退出客户程序。
*/
main(argc, argv)
int argc;
char *argv;
{
char cmd_buf[16];
struct in_addr sin_addr;
int sockno1, retcode;
char buf = “This is a string for test.“;
sin_addr.s_addr = inet_addr(“166.111.5.249”); / 运行服务器程序的主机的IP地址 /
if ((retcode = InitSocketsStruct(“TestService”)) < 0) { /* 初始化数据结构 */
printf(“InitSocketsStruct: error code = %d\n”, retcode);
exit(1);
}
while (1) {
printf(“>“);
gets(cmd_buf);
if (!strncmp(cmd_buf, “conn”, 4)) {
retcode = CreateConnection(&sin_addr); / 建立连接 /
printf(“return code: %d\n”, retcode);
}
else if(!strncmp(cmd_buf, “send”, 4)) {
printf(“Sockets Number:“);
scanf(“%d”, &sockno1);
retcode = SendPacket(sockno1, buf, 26); / 发送数据 /
printf(“return code: %d\n”, retcode, sizeof(buf));
}
else if (!strncmp(cmd_buf, “close”, 4)) {
printf(“Sockets Number:“);
scanf(“%d”, &sockno1);
retcode = CloseConnection(sockno1); / 关闭连接 /
linux下socket编程原理(十)
if the connection
is shutdown by peer then return 0, otherwise return 0-errno /
{
int actlen;
if ((Sockno >= 64) || (Sockno < 0) || (Mysock.Sockets[Sockno] == 0))
return(0);
if ((actlen = recv(Mysock.Sockets[Sockno], buf, size, 0)) < 0)
return(0-errno);
return(actlen); / actlen是接收的数据长度,如果为零,指示连接被对方关闭。/
}
2.5.3 简单服务器程序示例
/ File Name: server.c /
/ 这是一个很简单的重复服务器程序,它初始化好被动套接字后,循环等待接收连接。如果接收到连接,它显示数据套接字序号和客户端的IP地址;如果数据套接字上有数据到来,它接收数据并显示该连接的数据套接字序号和接收到的字符串。*/
main(argc, argv)
int argc;
char *argv;
{
struct in_addr sin_addr;
int retcode, i;
char buf[32];
/ 对于服务器程序,它经常是处于无限循环状态,只有在用户主动kill该进程或系统关机时,它才结束。对于使用kill强行终止的服务器程序,由于主套接字没有关闭,资源没有主动释放,可能会给随后的服务器程序重新启动产生影响。因此,主动关闭主套接字是一个良好的变成习惯。下面的语句使程序在接收到SIGINT、SIGQUIT和SIGTERM等信号时先执行CloseMainSock()函数关闭主套接字,然后再结束程序。因此,在使用kill强行终止服务器进程时,应该先使用kill -2 PID给服务器程序一个消息使其关闭主套接字,然后在用kill -9 PID强行结束该进程。/
(void) signal(SIGINT, CloseMainSock);
(void) signal(SIGQUIT, CloseMainSock);
(void) signal(SIGTERM, CloseMainSock);
if ((retcode = InitPassiveSock(“TestService”)) < 0) {
printf(“InitPassiveSock: error code = %d\n”, retcode);
exit(-1);
}
while (1) {
retcode = QuerySocketsMsg(); / 查询网络消息 /
if (SockMsg.AcceptNum == 1) { / 有外来连接等待接收?/
retcode = AcceptConnection(&sin_addr);
printf(“retcode = %d, IP = %s \n”, retcode, inet_ntoa(sin_addr.s_addr));
}
else if (SockMsg.AcceptNum == -1) / 主套接字错误?/
printf(“Daemon Sockets error.\n”);
for (i=0; i< * 接收外来数据 { i++)>
if ((retcode = RecvPacket(SockMsg.ReadQueue, buf, 32)) > 0)
printf(“sockno %d Recv string = %s \n”, SockMsg.ReadQueue, buf);
else / 返回数据长度为零,指示连接中断,关闭套接字。/
CloseConnection(SockMsg.ReadQueue);
}
} / end while /
}
2.5.4 简单客户程序示例
/ File Name: client.c /
/ 客户程序在执行时,先初始化数据结构,然后等待用户输入命令。它识别四个命令:
conn(ect): 和服务器建立连接;
send: 给指定连接发送数据;
clos(e): 关闭指定连接;
quit: 退出客户程序。
*/
main(argc, argv)
int argc;
char *argv;
{
char cmd_buf[16];
struct in_addr sin_addr;
int sockno1, retcode;
char buf = “This is a string for test.“;
sin_addr.s_addr = inet_addr(“166.111.5.249”); / 运行服务器程序的主机的IP地址 /
if ((retcode = InitSocketsStruct(“TestService”)) < 0) { /* 初始化数据结构 */
printf(“InitSocketsStruct: error code = %d\n”, retcode);
exit(1);
}
while (1) {
printf(“>“);
gets(cmd_buf);
if (!strncmp(cmd_buf, “conn”, 4)) {
retcode = CreateConnection(&sin_addr); / 建立连接 /
printf(“return code: %d\n”, retcode);
}
else if(!strncmp(cmd_buf, “send”, 4)) {
printf(“Sockets Number:“);
scanf(“%d”, &sockno1);
retcode = SendPacket(sockno1, buf, 26); / 发送数据 /
printf(“return code: %d\n”, retcode, sizeof(buf));
}
else if (!strncmp(cmd_buf, “close”, 4)) {
printf(“Sockets Number:“);
scanf(“%d”, &sockno1);
retcode = CloseConnection(sockno1); / 关闭连接 /