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;
&
[1] [2] 下一页