Socket套接字

 

套接字编程基础

一种双向的通信端口。位于网络中的主机通过连接的套接字提供的接口进行数据传输。

套接字与端口

套接字是一种使用标准UNIX文件描述符(file descriptor)与其他程序通信的方式。套接字可以看作是处于不同主机之间的两个程序的通信连接端点。一方面程序将要传输的信息写入套接字中,而另一方面则通过读取套接字内的数据来获得传输的信息。

图所示为使用套接字进行通信的示意图。假设存在两台主机A与B,在主机A中存在进程C,主机B中存在进程D,当进程C需要将数据送到进程D时,首先将数据写到套接字中,而进程D可以通过读取套接字来获得进程C发送的信息。

在网络中,不同计算机是通过IP地址来区分的,也就是说,要将数据由主机A发送到主机B,只要知道主机B的IP地址就可以确定数据要发送的目的地。但是,在主机A与B中不可能只有进程C和进程D两个进程。主机B在收到主机A发送来的数据后,如何才能确定该数据是发送给进程D?因此,还需要某种标识信息,用于描述网络通信数据发往的进程。TCP/IP协议提出了协议端口的概念,用于标识通信的进程。

进程与某个端口绑定后,操作系统会将收到的给该端口的数据送往该进程。与文件描述符类似,每个端口都有被称为端口号的整数类型的标识符,该标识符用于区分不同的端口。不同协议可以使用相同的端口号进行数据传输。例如,TCP使用了344的端口号,UDP同样可以使用344端口号进行数据传输。

端口号为一个16位的无符号整数,其取值范围为0~65535。低于256的端口被作为系统的保留端口号,主要用于系统进程的通信,不在这一范围的端口号被称为自由端口号,可以由进程自由使用。
 
一 3种类型的套接字:

1.流式套接字(SOCKET_STREAM)
    提供面向连接的可靠的数据传输服务。数据被看作是字节流,无长度限制。例如FTP协议就采用这种。
2.数据报式套接字(SOCKET_DGRAM)
    提供无连接的数据传输服务,不保证可靠性。
3.原始式套接字(SOCKET_RAW)
    该接口允许对较低层次协议,如IP,ICMP直接访问。

二 基本套接字系统调有有如下一些:
    创建套接字: socket()
    绑定本机端口: bind()
    建立连接: connect(),accept()
    侦听端口: listen()
    数据传输: send(), recv()
    输入/输出多路复用: select()
    关闭套接只:   closesocket()
   
三 数据类型
    struct sockaddr
    {
       unsigned short  sa_family;   //地址族, 一般为AF_INET
       char                  sa_data[14];   //14字节的协议地址
    }

    struct sockaddr_in
    {
       short int                     sin_family;   //地址族
       unsigned short int      sin_port;      //端口号
       struct in_addr             in_addr;      //ip地址
       unsigned char             sin_zero[8];  //填充
    }
 

服务器: server_addr.sin_addr = ADDR_ANY, 那么当 bind 调用的时候, 系统会accept 所有连接本机地址的客户端的连接请求.

客户端: connect调用时, 如果没有 bind, 系统则会自动分配一个本机 ip(如果你有双网卡) 和端口号 port 给当前连接. 如果需要调用 bind 明确绑定ip (如果不这么做, 服务器程序可能过滤掉一些非法ip), 但是我们又不知道那些可用port, 这时, 我们可以设置client_addr.sin_port = 0, 这样, 系统就在 bind client 和client_addr时, 自动分配一个可用的port, 而不用我们自己操心去找可用端口了.

四 常用函数:

    1 socket()
       头文件:
       #include <sys/types.h>
       #include <sys/socket.h>
       函数原型:
       int socket(int domain, int type, int protocol)
          domain: 协议类型,一般为AF_INET
          type: socket类型
          protocol:用来指定socket所使用的传输协议编号,通常设为0即可

          返回一个套接口描述符,如果出错,则返回-1。
一旦你有了一个套接口以后,下一步就是把套接口绑定到本地计算机的某一个端口上。但如果你只想使用connect()则无此必要。
    2 bind()
       头文件:
       #include <sys/types.h>
       #include <sys/socket.h>
       函数原型:
       int bind(int sockfd, struct sockaddr *my_addr, int addrlen)
          sockfd: socket描述符
          my_addr:是一个指向包含有本机ip地址和端口号等信息的sockaddr类型的指针
          addrlen:常被设为sizeof(struct sockaddr)
      如果出错,bind()也返回-1。


Code
 
如果你使用connect()系统调用,那么你不必知道你使用的端口号。当你调用connect()时,它检查套接口是否已经绑定,如果没有,它将会分配一个空闲的端口。
    3 connect()
       头文件:
       #include <sys/types.h> 
       #include <sys/socket.h>
       函数原型:
       int connect(int sockfd, struct sockaddr *serv_addr, int addrlen)
           sockfd: 目的服务器的socket描述符
           serv_addr:包含目的机器ip地址和端口号的指针
           addrlen:sizeof(struct sockaddr)
      如果出错,返回-1。
 
Code
 
 
如果你希望不连接到远程的主机,也就是说你希望等待一个进入的连接请求,然后再处理它们。这样,你通过首先调用listen(),然后再调用accept()来实现。
    4 listen()
       头文件:
       #include <sys/socket.h>
       函数原型:
       int listen(int sockfd, int backlog);
           sockfd:socket()系统调用返回的socket描述符
           backlog:指定在请求队列中的最大请求数,进入的连接请求将在队列中等待accept()它们。
      在远程的主机可能试图使用connect()连接你使用listen()正在监听的端口。但此连接将会在队列中等待,直到使用accept()处理它。调用accept()之后,将会返回一个全新的套接口文件描述符来处理这个单个的连接。这样,对于同一个连接来说,你就有了两个文件描述符。原先的一个文件描述符正在监听你指定的端口,新的文件描述符可以用来调用send()和recv()。
    5 accept()
       头文件:  
       #include <sys/types.h>
       #inlcude <sys/socket.h>
       函数原型:
       int accept(int sockfd, void *addr, int addrlen)
           sockfd:是被监听的socket描述符
           addr:通常是一个指向sockaddr_in变量的指针,该变量用来存放提出连接请求服务的主机的信息
           addrlen:sizeof(struct sockaddr_in)

Code
       
    6 send()
        头文件:
        #include <sys/socket.h>
        函数原型:
        int send(int sockfd, const void *msg, int len, int flags);
           sockfd:用来传输数据的socket描述符
           msg:要发送数据的指针
           flags: 0
系统调用send()返回实际发送的字节数,这可能比你实际想要发送的字节数少。如果返回的字节数比要发送的字节数少,你在以后必须发送剩下的数据。当send()出错时,将返回-1。
      
    7 recv()
        头文件:
        #include <sys/types.h>
        #include <sys/socket.h>
        函数原型:
        int recv(int sockfd, void *buf, int len, unsigned int flags)
          sockfd:接收数据的socket描述符
          buf:存放数据的缓冲区
          len:缓冲的长度
          flags:0

    8 sendto()
       头文件:
       #include <sys/types.h>
       #include <sys/socket.h>
       函数原型:
       int sendto(int sockfd, const void *msg, int len, unsigned int flags, const struct sockaddr *to, int tolen);
            除了两个参数以外,其他的参数和系统调用send()时相同。
            参数to是指向包含目的IP地址和端口号的数据结构sockaddr的指针。
            参数tolen可以设置为sizeof(structsockaddr)。
            系统调用sendto()返回实际发送的字节数,如果出错则返回-1。
        
    
    9 recvfrom()
       头文件:
       #include <sys/types.h>
       #include <sys/socket.h>
       函数原型:
       int recvfrom(int sockfd, void *buf, int len, unsigned int flags, struct sockaddr *from, int fromlen)
            参数from是指向本地计算机中包含源IP地址和端口号的数据结构sockaddr的指针。
            参数fromlen设置为sizeof(struct sockaddr)。
            系统调用recvfrom()返回接收到的字节数,如果出错则返回-1。

    
    10 read() write()
       int read(int fd, char *buf, int len)
       int write(int fd, char *buf, int len)
      
    11 shutdown()
       close(sockfd)
       int shutdown(int sockfd, int how)
第一个参数是你希望切断通信的套接口文件描述符。第二个参数how值如下:
0—Furtherreceivesaredisallowed
1—Furthersendsaredisallowed
2—Furthersendsandreceivesaredisallowed(likeclose())

 

 

getpeername()

这个系统的调用十分简单。它将告诉你是谁在连接的另一端:
#include<sys/socket.h>
int getpeername(int sockfd,struct sockaddr* addr,int* addrlen);
第一个参数是连接的数据流套接口文件描述符。
第二个参数是指向包含另一端的信息的数据结构sockaddr的指针。
第三个参数可以设置为sizeof(structsockaddr)。
如果出错,系统调用将返回-1。
一旦你获得了它们的地址,你可以使用inet_ntoa()或者gethostbyaddr()来得到更多的信息。


gethostname()


系统调用gethostname()比系统调用getpeername()还简单。它返回程序正在运行的计算机的名字。系统调用gethostbyname()可以使用这个名字来决定你的机器的IP地址。
下面是一个例子:
#include<unistd.h>
int gethostname(char*hostname,size_tsize);
如果成功,gethostname将返回0。如果失败,它将返回-1。

 

 

   OSI            TCP/IP模型
   应用层            应用层
   表示层
   会话层
   传输层            传输层
   网络层            网络层
   数据链路层       网络接口
   物理层

   相同层次之间不可以直接通信,是虚拟通信。下层向上层提供服务,实际通信在最底层完成。各层之

间单向依赖。

   应用层:Telnet、FTP、HTTP、DNS、SMTP、POP3。
   传输层:TCP实现数据的完整性,例如下载安装包;UDP实现数据的及时性,例如网络视频电话。


  TCP和UDP可以使用同样的端口,1024以下的端口保留给预定义的服务。windows socket只支持

AF_INET网际域.



1、基于TCP的socket编程

   服务器端程序:
   创建套接字;
   将套接字绑定到一个本地地址和端口上bind
   将套接字设为监听模式,准备接受客户请求listen
   等待客户请求,当请求到来,返回一个新的对应于此次连接的套接字accept
   用返回的套接字和客户端进行通信send/recv。
   返回,等待另一客户的请求。
   关闭套接字。

   客户端程序:
   创建套接字;
   向服务器发送连接请求connect
   和服务器进行通信send/recv
   关闭套接字。
2、基于UDP的socket编程

   服务器端(接收端)程序:
   创建套接字
   绑定套接字到本地地址和端口上bind
   等待接受数据recvfrom
   关闭套接字

   客户端(发送端)程序:
   创建套接字
   发送数据sendto
   关闭套接字
   127.0.0.1是回路地址。可以在同一台机子上通信。


基于TCP/IP的socket代码:

 

 

Code

 

posted @ 2009-06-18 18:39  辛勤耕耘  阅读(931)  评论(0)    收藏  举报