socket-program.txt

来自「学习linux的工具书」· 文本 代码 · 共 1,433 行 · 第 1/3 页

TXT
1,433
字号
      调用socket()函数创建一个套接字
      
      调用connect()函数试图连接服务器
      
      如果连接成功调用write()函数请求数据,调用read()函数接收引入的应答
      
      不连接(Connectionless)套接字程序设计  
            
   现在让我们来考虑一下不连接的信息交换.其服务器端的原理和面向连接的协议有所不
同.服务器并不调用listen和accept而是调用recvfrom().同样,服务器用sendto()函数来
应答信息.服务器端程序见程序清单59.3.

                                  程序清单59.3
                                  
                                    服务器端
                                    
#include    <sys/types.h>

#include    <sys/socket.h>

#include <linux/in.h>

#include <linux/net.h>



#define MY_PORT 6545

#define MAXM   4096

char mesg[MAXM];



main(int argc, char *argv[])

{

int sockfd, newfd;

int cpid; /* child id */

struct sockaddr_in servaddr;

struct sockaddr_in clientInfo;



if ((sockfd = socket(AF_INET, SOCK_STREAM, 0) < 0)

    {

    myabort("Unable to create socket");

    }

#ifdef LINUX

opt = 1; len = sizeof(opt);

setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,&len);

#endif



bzero((char *)&servaddr, sizeof(servaddr));



servaddr.sin_family = AF_INET;

servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

servaddr.sin_family = htons(MY_PORT);



/*

* The htonl (for a long integer) and htons (for short integer) convert

* a host oriented byte order * into a network order.

*/





if (bind(sockfd,(struct sockaddr *)&servaddr,sizeof(struct sockaddr)) < 0)

    {

    myabort("Unable to bind socket");

    }





for (;;)

    {

    /* wait here */

                         n = recvfrom(sockfd, mesg, MAXM, 0,

                         (struct sockaddr *)&clientInfo,

                          sizeof(struct sockaddr));



                     doSomethingToIt(mesg);



                     sendto(sockfd,mesg,n,0,

                     (struct sockaddr *)&clientInfo,

                      sizeof(struct sockaddr));

 }



} 

   看见了吗,处理每个消息只调用了两个函数,这比面向连接的协议更容易.但你必须,无论
如何,得在同一时间处理每个消息,因为消息从多台客户机向服务器涌来.而在面向连接的协
议中,子进程总是知道每条消息从哪里来.

   客户机同样不能调用connect()系统调用.但是客户机可以直接调用sendto()函数.客户
机端和服务器端大致相同.只是它在调用recvfrom()之前调用sendto():

#include <sys/types.h>

#include <sys/socket.h>



int sendto((int sockfd,

 const void *message__,  /* the pointer to message */

 int  length, /* of message */

 unsigned  int type, /* of routing, leave 0 */

 const struct sockaddr * client, /* where to send it */

 int length ); /* of sockaddr */

   注意:如果你使用的是BSD系统,请使用sendto()系统调用,不要使用sendmsg(),因为send
to()性能更好.

   如出错则返回-1,不过仅能检查出本地错误.
   
   recvfrom()系统调用是这样定义的:
   
#include <sys/types.h>

#include <sys/socket.h>



int recvfrom(int sockfd,

 const void *message__,  /* the pointer to message */

 int  length, /* of message */

 unsigned  int flags, /* of routing, leave 0 */

 const struct sockaddr * client, /* where to send it */

 int length ); /* of sockaddr */

   如果一个信息大得缓冲区都放不下,那么附加信息将被砍掉.该调用可以立即返回,也可
以永久的等待.这取决于你把flags设置成什么类型.你甚至可以设置超时(timeout)值.在说
明书(man pages)中可以找到recvfrom的更多信息.

   在此你已学会了如何利用Linux的性能上的优点设计网络应用程序的基本知识.我们不打
算再进一步的描述更复杂的网络编程了.获得更多细节信息的一个极好的起点是参考W. Ric
hard Stevens 的<<Unix 网络程序设计>>(Prentice Hall, 1990).此书乃众多大学所使用
的经典教材,内容极为详尽.

                                  记录和文件锁定
                                  
   当两个进程共享一个文件时,这之中存在一件非常危险的事情.如果一个进程改变了文件
目录那么必然影响到另一个进程.基于此理由,大多数操作系统采用一个互斥原则(mutually
exclusive principle):当一个进程拥有一个文件时,其它进程就不能再碰这个文件.这叫做
文件锁定.

   这个技术非常容易实现.通常所发生的事是,所谓"锁定文件"就是创建一个和源文件名同
名的文件再加上.lock扩展名.这就告诉其它进程这个文件不能再碰了.Linux假脱机打印系
统以及UUCP就是这样实现文件锁定的.这可能是一种粗暴的方法,但编程上非常简单.

   不幸的是,你有几个进程要同时迅速的处理同一条信息时,这项技术对你并不实用.因为
等待文件打开和关闭所产生的延时将变得很长.同样一个进程如不能正确的释放文件,其它
进程将会挂在那里一直等待下去以获得存取权限.

   由于这个原因,通常使用记录锁定.用记录锁定,一个大文件的一小部分被锁定以防止两
个进程同时改变它.如果有必要的话,记录锁定可以让多个进程同时存取相同文件的不同部
分记录.当然实现记录锁定编程要比实现文件锁定更复杂.

   通常,要实现记录锁定你需要用到文件偏移量或是到文件起始处的字符数.在大多数程序
中,一个范围内的字符被锁定.程序记录锁定范围的起始处和长度,并保存在其它进程能查询
到的地方.不管是编写文件锁定还是记录锁定都需要对操作系统有很好的理解.但是并不难.
特别是可以从Internet,网络程序设计指导书和BBS上很容易地获得成千的程序.你可以察看
这些程序的源代码.

                                  进程间通信
                                  
   网络程序设计中通常包括两个或更多的进程将互相对话(interprocess communications
).因此进程通信的方法在网络程序设计中是极为重要的.网络程序设计在一些重要的方面不
同于一般程序设计通常所使用的方法.一个传统的程序可以通过全局变量或函数调用和不同
的模块(甚至同一机器上的其它应用程序)对话.但是在网络上却不行.

   网络程序设计的一个重要的目标是保证进程间不互相干涉.否则系统可能被挂起或自锁.
因此,进程间必须使用简洁有效的方法进行通信.在此方面,UNIX具有非常显著的健壮性.因
为UNIX的许多基本性能如管道,队列等都非常适合网络.

   和单个的应用程序代码相比,写进程间通信的代码十分复杂.如果你想写这类程序,可以
学习网络程序设计指导书和BBS站点上的例子程序.以了解这些任务是何以完成的.

                                    小结
                                    
   很少有人想写网络应用程序,因此进程的细节最好留给那些想写的人.实践和查阅大量的
例子程序是开始写网络代码的最好的方法.但是要掌握这门技术却要花许多年时间.



-------------------------------英文原文--------------------------------


 
 
    - 59 -
        Network Programming
            Ports and Sockets
            Socket Programming
                The socket() System Call
                The bind() System Call
                The listen() System Call
                The accept() System Call
                The setsockopt() and getsockopt() System Calls
                The connect() System Call
            Listing 59.1. The server side for a socket-oriented protocol.
            Listing 59.2. The client side function.
                Connectionless Socket Programming
            Listing 59.3. The server side.
            NOTE
            Record and File Locking
            Interprocess Communications
            Summary



- 59 -
Network Programming
by Kamran Husain and Tim Parker
IN THIS CHAPTER
    Ports and Sockets
    Socket Programming
    Record and File Locking
    Interprocess Communications

This chapter looks at the basic concepts you need for network programming:
    Ports and sockets
    Record and file locking
    Interprocess communications
It is impossible to tell you how to program applications for a network in just a
few pages. Indeed, the best available reference to network programming takes
almost 800 pages in the first volume alone! If you really want to do network
programming, you need a lot of experience with compilers, TCP/IP, and network
operating systems--and you need a great deal of patience.
For details on TCP/IP, check the book Teach Yourself TCP/IP in 14 Days, by Tim
Parker (Sams Publishing).
Ports and Sockets
Network programming relies on the use of sockets to accept and transmit
information. Although there is a lot of mystique about sockets, the concept is
actually simple to understand.
Most applications that use the two primary network protocols, Transmission
Control Protocol (TCP) and User Datagram Protocol (UDP) have a port number that
identifies the application. A port number is used for each different application
the machine is handling, so it can keep track of those applications by numbers
rather than names. The port number makes it easier for the operating system to
know how many applications are using the system and which services are
available.
In theory, port numbers can be assigned on individual machines by the system
administrator, but some conventions have been adopted to allow better
communications. These conventions enable the port number to identify the type of
service that one system is requesting from another. For this reason, most
systems maintain a file of port numbers and their corresponding services.
Port numbers are assigned starting from the number 1. Normally, port numbers
above 255 are reserved for the private use of the local machine, but numbers
between 1 and 255 are used for processes requested by remote applications or for
networking services.
Each network communications circuit into and out of the host computer's TCP
application layer is uniquely identified by a combination of two numbers,
together called the socket. The socket is composed of the IP address of the
machine and the port number used by the TCP software.
Because at least two machines are involved in network communications, there will
be a socket on both the sending and the receiving machine. Because the IP
address of each machine is unique and the port numbers are unique to each
machine, socket numbers are also unique across the network. This setup enables
an application to talk to another application across the network based entirely
on the socket number.
The sending and receiving machines maintain a port table that lists all active
port numbers. The two machines involved have reversed entries for each session
between the two, a process called binding. In other words, if one machine has
the source port number 23 and the destination port number set at 25, the other
machine has its source port number set at 25 and the destination port number set
at 23.
Socket Programming
Linux supports BSD-style socket programming. Both connection-oriented and
connectionless types of sockets are supported. In connection-oriented
communication, the server and client establish a connection before any data is
exchanged. In connectionless communication, data is exchanged as part of a
message. In either case, the server always starts first, binds itself to a
socket, and listens to messages. How the server attempts to listen depends on
the type of connection for which you have programmed it.
You need to know about a few system calls:
    socket()
    bind()
    listen()
    accept()
    setsockopt() and getsockopt()
    connect()
    sendto()
   
    recvfrom()
We will cover these system calls in the following examples.
The socket() System Call
The socket() system call creates a socket for the client or the server. The
socket function is defined as shown here:
#include<sys/types.h>

#include<sys/socket.h>

int socket(int family, int type, int protocol)


For Linux, you will have family = AF_UNIX. The type is either SOCK_STREAM for
reliable, though slower, communications or SOCK_DGRAM for faster, but less
reliable, communications. The protocol should be IPPROTO_TCP for SOCK_STREAM and
IPPROTO_UDP for SOCK_DGRAM.
The return value from this function is -1 if there was an error; otherwise, it's
a socket descriptor. You will use this socket descriptor to refer to this socket
in all subsequent calls in your program.
Sockets are created without a name. Clients use the name of the socket to read
or write to it. This is where the bind function comes in.
The bind() System Call
The bind() system call assigns a name to an unnamed socket. The bind function is
defined like this:
#include<sys/types.h>

#include<sys/socket.h>



int bind(int sockfd, struct sockaddr *saddr, int addrlen)


The first item is a socket descriptor. The second is a structure with the name
to use, and the third item is the size of the structure.
Now that you have bound an address for your server or client, you can connect()
to it or listen on it. If your program is a server, it sets itself up to listen
and accept connections. Let's look at the function available for such an
endeavor.
The listen() System Call
The listen() system call is used by the server. It is defined in the following
way:
#include<sys/types.h>

#include<sys/socket.h>



int listen(int sockfd, int backlog);


The sockfd is the descriptor of the socket. The backlog is the number of
connections that are pending at one time before any are rejected. Use the
standard value of 5 for backlog. A returned value of less than 1 indicates an
error.
If this call is successful, you can accept connections.
The accept() System Call
The accept() system call is used by a server to accept any incoming messages
from clients' connect() calls. Be aware that this function does not return if no
connections are received. It is defined like this:
#include<sys/types.h>

#include<sys/socket.h>



int accept(int sockfd, struct sockaddr *peeraddr, int addrlen)


The parameters are the same as those for the bind call, with the exception that
the peeraddr points to information about the client that is making a connection
request. Based on the incoming message, the fields in the structure pointed at
by peeraddr are filled out.
The setsockopt() and getsockopt() System Calls
The socket libraries provided with Linux include a bug. The symptom of this bug
is that you cannot reuse a port number for a socket even if you closed the
socket properly. For example, say you write your own server that waits on a
socket. This server opens the socket and listens on it with no problems.
However, for some reason (a crash or normal termination), when the program is
restarted, you are not able to bind to the same port. The error codes from the
bind() call will always return an error indicating that the port you are
attempting to connect to is already bound to another process.
The problem is that the Linux kernel never marks the port as unused when the
process bound to a socket terminates. In most other UNIX systems, the port can
be used again by another invocation of the same or even another process.
The way to get around this problem in Linux is to use the setsockopt() system
call to set the options on a socket when it is opened and before a connection is
made on it. The setsockopt() sets options and the getsockopt()call gets options
for a given socket.
Here is the syntax for these calls:
#include<sys/types.h>

#include<sys/socket.h>



int getsockopt(int sockfd, int level, int name, char *value, int *optlen)


int setsockopt(int sockfd, int level, int name, char *value, int *optlen)


The sockfd must be an open socket. The level is the protocol level to use for
the function (IPPROTO_TCP for TCP/IP and SOL_SOCKET for socket level options),
and the name of the option is as defined in the socket's man page. The *value
pointer points to a location where a value is stored for getsockopt() or when a
value is read for setsockopt(). The optlen parameter is a pointer to an integer
containing the length of the parameters in bytes; the value is set by
getsockopt() and must be set by the programmer when making a call via
setsockopt().
The full man page with details of all the options is found in the man page
setsockopt(2).
Now back to the bug in Linux. When you open a socket, you must also call the
setsockopt() function with the following segment of code:
#ifdef LINUX

opt = 1; len = sizeof(opt);

setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,&len);



#endif


The #ifdef and #endif statements are necessary only if you want to port the code
over to systems other than Linux. Some UNIX systems might not support or require
the SO_REUSEADDR flag.
The connect() System Call
The connect() system call is used by clients to connect to a server in a
connection-oriented system. This connect() call should be made after the bind()
call. It is defined like this:
#include<sys/types.h>

#include<sys/socket.h>



int connect(int sockfd, struct sockaddr *servsaddr, int addrlen)


The parameters are the same as those for the bind call, with the exception that

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?