分享
 
 
 

Linux网络编程,第一部分:BSD套接口篇(下)(翻译)

王朝system·作者佚名  2006-01-09
窄屏简体版  字體: |||超大  

(接上篇)

2、建立对应的客户端

正如你正要看到的,相比服务端,客户端的代码就要简单多了。在这个程序中你必须提供两个命令行参数:服务端所在机器主机名或IP地址,和服务段绑定的端口。当然,服务端还必须在客户端运行以前就已经正常运行:P。

/*

* Listing 2:

* An example client for "Hello, World!" server

* Ivan Griffin (ivan.griffin@ul.ie)

*/

/* Hellwolf Misty translated */

#include <stdio.h> /* perror() */

#include <stdlib.h> /* atoi() */

#include <sys/types.h>

#include <sys/socket.h>

#include <unistd.h> /* read() */

#include <netinet/in.h>

#include <arpa/inet.h>

#include <netdb.h>

int main(int argc, char *argv[])

{

int clientSocket,

remotePort,

status = 0;

struct hostent *hostPtr = NULL;

struct sockaddr_in serverName = { 0 };

char buffer[256] = "";

char *remoteHost = NULL;

if (3 != argc)

{

fprintf(stderr, "Usage: %s

\n",

argv[0]);

exit(1);

}

remoteHost = argv[1];

remotePort = atoi(argv[2]);

clientSocket = socket(PF_INET, SOCK_STREAM,

IPPROTO_TCP);

if (-1 == clientSocket)

{

perror("socket()");

exit(1);

}

/*

* 首先假定是DNS主机名

* 注:

* struct hostent{

* char *h_name; /* official name of host */

* char **h_aliases; /* alias list */

* int h_addrtype; /* host address type */

* int h_length; /* length of address */

* char **h_addr_list; /* list of addresses from name server */

* };

* #define h_addr h_addr_list[0]

* 注意到了吗?h_addr是一个宏,如果你用gdb调试时

* display phostent->h_addr出错的话不要奇怪

*/

hostPtr = gethostbyname(remoteHost); /* struct hostent *hostPtr; */

if (NULL == hostPtr)

{/* 不是?? */

hostPtr = gethostbyaddr(remoteHost,

strlen(remoteHost), AF_INET);/* 应该是点分形式的IP地址吧*/

if (NULL == hostPtr) /* 还不是,!-_- */

{

perror("Error resolving server address");

exit(1);

}

}

serverName.sin_family = AF_INET;

serverName.sin_port = htons(remotePort);

(void) memcpy(&serverName.sin_addr,

hostPtr->h_addr,

hostPtr->h_length);

/* 这里并不需要再bind了,因为connect已经可以为我们解决一切 */

status = connect(clientSocket,

(struct sockaddr*) &serverName,

sizeof(serverName));

if (-1 == status)

{

perror("connect()");

exit(1);

}

/* connect成功后,一个双工(duplex)的网络连接就被建立好了

* 像服务器一样,客户端可以使用read()和write()接收数据

/*

* 客户端的具体代码应该从这里开始实施

* 比如从服务端接受和回应信息等等

*/

while (0 < (status = read(clientSocket,

buffer, sizeof(buffer) - 1)))

{

printf("%d: %s", status, buffer);

/* 注:如果读成功,status表示获得的字节数(包括'\0') */

}

if (-1 == status)

{

perror("read()");

}

close(clientSocket);

return 0;

}

需要注意的几点:

发送文字通常都工作正常。但是记住不同的系统对换行的实现有差别(比如,Unix使用\0x12,而微软使用\0x15\0x12)。

不同的实现可能使用不同的字节序(byte-order)。但不用担心,BSD的设计者们在一考虑了这一点。有很多现成的函数实现了这种转换,他们都有一定的命名规则:htons代表实现host-to-network的short结构的转换,还有htonl,ntohs,ntohl,也很容易判断出他们的工作方式。至于网络字节序是大端对齐(big-endian)还是小端对齐(little-endian)并不是个问题,因为在TCP/IP网络上它已经被标准化了(注:网络字节序用的是大端对齐)。当然除非你一直在网络上发一个字符(注:还得是ASCII的),不用这些转换函数不会引起大问题,但通常情况下,你会遭遇到字节序问题。这还要看你的机器,有时侯这些函数就是一个空宏,有时候它们确实是函数。有趣的是,最常见的网络编程的bugs来源是忘记了在填充sockaddr_in结构的sin_addr.s_addr字段时忘记了使用这些函数,即使使用INADDR_ANY也必须这么做。 网络编程的一个重要目标就是不给双方带来不可预料的麻烦。比如说,服务器在访问关键数据时必须通过必要的机制同步对这部分资源的访问,避免由此带来的死锁并且保证数据的有效性。 大多数情况下,你不能从机器间传递指针并试图使用它。

<>类似的,大多数情况下,你也不能试图通过套接口传递一个文件描述符从一个进程到另一个进程(非子进程)并直接使用它。BSD和SVR4系统提供了在不相关的进程间传递文件描述符的不同方法;然而,在linux下最简单的方法就是通过使用/proc文件系统。

此外,你必须保证你正确的解决了short writes问题。Short write发生在write()调用仅仅部分的将缓冲区写给一个文件描述符对应的设备。它们发生的原因归咎于操作系统的缓冲区,和底层传输系统的流控制系统。某些系统调用,通常被称作慢系统调用(slow system calls)可能会被(其他调用)中断。一些可能不会被自动重起,所以你必须明确地在网络编程时解决这一问题。下面的代码解决了short write问题:

/*

* Listing 3:

* Handling short writes

* Ivan Griffin (ivan.griffin@ul.ie)

*/

/* Hellwolf Misty translated */

int bytesToSend = 0,

bytesWritten = 0,

num = 0; /*

* 这里用到的bytesToSend, buffer, and

* fileDesc必须已经在其他某个地方有定义.

*/

for (bytesWritten = 0; bytesWritten < bytesToSend;

bytesWritten += num)

{

num = write(fileDesc,

(void *)( (char *)buffer +

(char *)bytesWritten ),

bytesToSend - bytesWritten);

if (num < 0)

{

perror("write()");

if (errno != EINTR)

{

exit(1);

}

}

}

使用多线程而不是多进程可能会减轻服务器的负担,并且更加有效。线程间上下文的转化(当然是指同一个进程空间)通常开销比进程间上下文转换小得多。然而,如此多的子线程都在操作网络I/O,如果它们在内核级还可以,但如果它们是用户级的,整个进程都会因为第一个调用I/O的线程而阻塞。这将会导致不愿看到的其他线程的饥饿状态直到I/O的完成。正如你看到的,当使用简单的forking模型时在父进程和子进程中关闭不必要的套接口文件描述符是相当寻常的。这保护了进程潜在的错误读写这些描述符的可能性。但是不要试图在使用线程模型时这样做,进程中的多线程共享同一个内存虚拟地址空间和文件描述符集。如果你在一个线程中关闭了一个描述符,那么进程中的其他所有的线程都将无法得到该描述符。

3、无连接的数据传输——UDP

下面的代码显示了一个使用UDP的服务端。UDP程序很像TCP程序,但他们又很大的不同。首先,UDP不保证可靠的传输——如果你需要在使用UDP时获得可靠性,你必须或者自己实现或者转而用TCP。

像TCP程序一样,用UDP你可以建立一个套接口并将其绑定到特定地址。UDP服务端不监听(listen)和接受(accept)外来的连接,客户也不必显式的连接到服务器。事实上,在UDP客户端和服务段之间并没有太大的区别。服务端必须绑定到一个确定的端口和地址好让客户端知道向哪里发送数据。而且当你的服务端使用send(),客户端也应该使用对应的recv族函数。

UDP服务端程序清单:

/*

* Listing 4:

* Example UDP (connectionless) server

* Ivan Griffin (ivan.griffin@ul.ie)

*/

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <sys/uio.h>

#include <sys/types.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <arpa/inet.h>

#include <netdb.h>

#define MAX_MESG_SIZE 4096

char mesg[MAX_MESG_SIZE] = "";

int main(int argc, char *argv[])

{

int udpSocket = 0,

myPort = 0,

status = 0,

size = 0,

clientLength = 0;

struct sockaddr_in serverName = { 0 },

clientName = { 0 };

if (2 != argc)

{

fprintf(stderr, "Usage: %s \n",

argv[0]);

exit(1);

}

myPort = atoi(argv[1]);

udpSocket = socket(PF_INET, SOCK_DGRAM, /* PF_INET和SOCK_DGRAM组合代表了UDP */

IPPROTO_UDP);

if (-1 == udpSocket)

{

perror("socket()");

exit(1);

}

memset(&serverName, 0, sizeof(serverName));

memset(&clientName, 0, sizeof(clientName));

serverName.sin_family = AF_INET;

serverName.sin_addr.s_addr = htonl(INADDR_ANY);

serverName.sin_port = htons(myPort);

status = bind(udpSocket, (struct sockaddr *)

&serverName, sizeof(serverName));

if (-1 == status)

{

perror("bind()");

exit(1);

}

for (;;)

{

/* ssize_t recvfrom(int s, void *buf, size_t len, int flags, struct sock-

* addr *from, socklen_t *fromlen);

* /

size = recvfrom(udpSocket, mesg,

MAX_MESG_SIZE, 0,

(struct sockaddr *) &clientName,

&clientLength);

if (size == -1)

{

perror("recvfrom()");

exit(1);

}

/* ssize_t sendto(int s, const void *msg, size_t len, int flags, const

* struct sockaddr *to, socklen_t tolen);

*/

status = sendto(udpSocket, mesg, size, 0,

(struct sockaddr *) &clientName,

clientLength);

if (status != size)

{

fprintf(stderr,

"sendto(): short write.\n");

exit(1);

}

}

/* never reached */

return 0;

}

将TCP的客户端改写成UDP的客户端将留作一个练习

4、/etc/services文件

为了连接到一个服务端,你必须首先知道其监听的地址和端口。许多常见的服务(FTP,TELNET等等)的信息都列在了一个文本文

件/etc/services中。getservbyname()函数可以用名称询问一个服务的详细情况包括它的端口号(注意!它已经是网络字节序了),它的原型在

/usr/include:

struct servent *getservbyname(const char *name, const char *proto); 默认的proto是"tcp"

struct servent

{

char *s_name; /* official service name */

char **s_aliases; /* alias list */

int s_port; /* port number, network

* byte-order--so do not

* use host-to-network macros */

char *s_proto; /* protocol to use */

};

5、总结

这篇文章介绍了Linux下使用C和BSD Socket API进行网络编程。总的来说,用这套API来写代码将是相当耗费劳力的,特别是与其他一些

技术相比。在今后的文章中,我会将BSD Socket API与另外两个可以选择的Linux下的方法进行比较:远程过程调用(RPCs,Remote Procedure

Calls)和CORBA(Common Object Request Broker Achitecture,公用对象请求代理[调度]程序体系结构)。RPCs在Ed Petron的文章"Remote

Procedure Calls"(Linux Journal Issue #42,1997年十月)中有介绍。

相关资源:

UNIX Network Programming, W. Richard Steves, Prentice Hall, 1990.

An Introductory 4.4BSD Interprocess Communication Tutorial, Stuart Sechrest, University of California, Berkeley. 1986.

Available via anonymous FTP at: ftp://ftp.NetBSD.ORG/pub/NetBSD/misc/lite2-docs/psd/20.ipctut.ps.gz.

An Advanced 4.4BSD Interprocess Communication Tutorial, Samuel J. Leffler, Robert S. Fabry, William N. Joy, Phil Lapsley,

University of California, Berkeley. 1986 Available via anonymous FTP at: ftp://ftp.NetBSD.ORG/pub/NetBSD/misc/lite2-

docs/psd/21.ipc.ps.gz.

Java and the Client-Server, Joe Novosel, Linux Journal, Issue 33, January 1997.

RFC 793: Transmission Control Protocol, J. Postel (ed.), September 1981. Available via HTTP at

http://www.internic.net/rfc/rfc793.txt.

RFC 1337: TIME-WAIT Assassination Hazards in TCP, R. Braden, May 1992. Available via HTTP at

http://www.internic.net/rfc/rfc1337.txt.

Programming UNIX Sockets in C FAQ, Vic Metcalfe, Andrew Gierth and other contributers, February 1997. Available via HTTP at

http://kipper.york.ac.uk/~vic/sock-faq/html/unix-socket-faq.html

作者简介:

Ivan Griffin is a research postgraduate student in the ECE department at the University of Limerick, Ireland. His interests

include C++/Java, WWW, ATM, the UL Computer Society (http://www.csn.ul.ie/) and, of course, Linux

(http://www.trc.ul.ie/~griffini/linux.html). His e-mail address is ivan.griffin@ul.ie.

Dr. John Nelson is a senior lecturer in Computer Engineering at the University of Limerick. His interests include mobile

communications, intelligent networks, Software Engineering and VLSI design. His e-mail address is john.nelson@ul.ie.

所有的程序都可以在这里获得: ftp://ftp.ssc.com/pub/lj/listings/issue46/2333.tgz

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有