分类:
2006-03-24 11:51:43
(接上篇)
2、建立对应的客户端
正如你正要看到的,相比服务端,客户端的代码就要简单多了。在这个程序中你必须提供两个命令行参数:服务端所在机器主机名或IP地址,和服务段绑定的端口。当然,服务端还必须在客户端运行以前就已经正常运行:P。
/*
* Listing 2:
* An example client for "Hello, World!" server
* Ivan Griffin ()
*/
/* Hellwolf Misty translated */
#include
#include
#include
#include
#include
#include
#include
#include
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
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;
}
需要注意的几点:
/*
* 这里用到的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 ()
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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
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: .
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: -
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
.
RFC 1337: TIME-WAIT Assassination Hazards in TCP, R. Braden, May 1992. Available via HTTP at
.
Programming UNIX Sockets in C FAQ, Vic Metcalfe, Andrew Gierth and other contributers, February 1997. Available via HTTP at
作者简介:
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 () and, of course, Linux
(). His e-mail address is .
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 .
所有的程序都可以在这里获得: