曲径通幽论坛

 找回密码
 立即注册
搜索
查看: 9433|回复: 6
打印 上一主题 下一主题

套接字

[复制链接]

4917

主题

5879

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34382
跳转到指定楼层
楼主
发表于 2009-7-9 23:42:26 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
一、套接字地址结构
linux/socket.h 中定义了一个通用的套接字地址:
struct sockaddr {
           unsigned short     sa_family;              /* 地址类型, AF_xxx */
           char                       sa_data[14];         /*  14字节的协议地址 */
};
成员 sa_family 表示套接字的协议族类型,对应于 TCP/IP 协议该值为 AF_INET;
成员 sa_data 存储具体的协议地址。
sa_data 之所以定义为 14 字节,因为有的协议族使用较长的地址格式。

注意,一般编程中,并不对这个结构进行操作,而是使用另一个与它等价的数据结构 sockaddr_in

每种协议族都有自己的协议地址格式, TCP/IP 协议族的地址格式为结构体 struct sockaddr_in , 它在 netinet/in.h 头文件中有定义:
struct sockaddr_in {
        unsigned short          sin_family;     /*地址类型*/
        unsigned short int      sin_port;       /*端口号*/ 
        struct in_addr          sin_addr;       /*IP地址*/  
        unsigned char           sin_zero[8];    /*填充字节,一般赋值为0*/  
};
成员 sin_family 表示地址类型,对于使用 TCP/IP 协议进行的网络编程,该值只能是 AF_INET。
成员 sin_port 是端口号。
成员 sin_addr 用来存储 32 位的 IP 地址。
数组 sin_zero 为填充字段,一般赋值为 0 。

struct in_addr 结构定义如下:
struct in_addr {
          unsigned long   s_addr;
};


结构体 sockaddr 的长度为 16 字节,结构体 sockaddr_in 的长度也是 16 字节。通常在编写基于 TCP/IP 协议的网络程序时,使用结构体 sockaddr_in 来设置地址,然后通过强制类型转换成 sockaddr 类型。

如下示例设置地址信息代码:
struct sockaddr_in sock;
sock.sin_family = AF_INET;
sock.sin_port = htons(80);   /*设置端口号80*/
sock.sin_addr.s_addr = inet_addr("222.186.13.56");   /*设置地址*/
memset(sock.sin_zero, 0, sizeof(sock.sin_zero));

htons() 和  inet_addr() 都是转换函数。

memset() 函数的原型为:
memset (void *s, int c, size_t n);
它将 s 指向的内存区域的前 n 个字节赋值由参数 c 指定的值。

4917

主题

5879

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34382
沙发
 楼主| 发表于 2009-7-10 01:19:19 | 只看该作者

创建套接字

二、创建套接字
创建套接字的函数是 socket() ,其原型如下:
#include <sys/types.h>
#include <sys/socket.h>

int socket(int domain, int type, int protocol);
参数说明
( 1 )domain : < 指定创建套接字所使用的协议族,它们在头文件 linux/socket.h 中定义 >
AF_UNIX : 创建只在本机内进行通信的套接字。
AF_INET : 使用 IPv4 TCP/IP 协议。
AF_INET6 : 使用 IPv6 TCP/IP 协议。
AF_LOCAL : Unix 域协议
AF_ROUTE : 路由套接口
AF_KEY : 密钥套接口

( 2 ) type : < 指定套接字类型 >
SOCK_STREAM : 创建 TCP 流套接字。
SOCK_DGRAM : 创建 UDP 数据报套接字。
SOCK_RAW : 创建原始套接字。

( 3 ) protocol :
通常设置为 0 ,表示通过参数 domain 指定的协议族和参数 type 指定的套接字类型来确定使用的协议。当创建原始套接字时( 指定 type 为 SOCK_RAW ),系统无法唯一地确定协议,此时就需要使用该参数指定所使用的协议。

函数执行成功就返回一个新创建的套接字,有错误就返回 -1 。

创建套接字代码示例:
int sock_fd;
sock_fd = socket (AF_INET, SOCK_STREAM, 0);
if (sock_fd < 0) {
     perror("socket");
     exit(1);
}

4917

主题

5879

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34382
板凳
 楼主| 发表于 2009-7-10 19:04:18 | 只看该作者

建立连接

三、建立连接
connect() 函数用来在一个指定的套接字上创建一个连接,其函数原型如下:
 
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);
sockfd 是一个由函数 socket 创建的套接字。如果该套接字的类型是 SOCK_STREAM,则 connect() 函数用于向服务器发送连接请求,服务器的 IP 地址和端口号由参数 serv_addr 指定。如果套接字的类型是 SOCK_DGRAM,则 connect() 函数并不建立真正的连接,它只是告诉内核与该套接字进行通信的目的地址(由第二个参数指定),只有该目的地址发来的数据才会被 socket 接收。对于 SOCK_DGRAM 类型的套接字,调用 connect() 函数的好处是不必每次发送和接收数据时指定目的地址。
通常一个面向连接的套接字(如 TCP 套接字)只能调用一次 connect() 函数。而对于无连接的套接字(如 UDP 套接字)则可以多次调用 connect() 函数以改变与目的地址的绑定。将参数中的 serv_addr 中的 sa_family 设置为 AF_UNSPEC 可以取消绑定。
addrlen 参数是 serv_addr 的长度。
函数执行成功时返回0,出错时返回 -1,错误代码存入 errno 中。
该函数的常见用法如下
struct sockaddr_in      serv_addr;
memset(&serv_addr, 0, sizeof(struct sockaddr_in));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(80);
if (inet_aton("222.186.13.56", &serv_addr.sin_addr) < 0) {
        perror("inet_aton");
        exit(1);
}
/*使用 sock_fd 套接字连接到由 serv_addr 指定的目的地址上,这里假定 sock_fd 已定义*/
if (connect(sock_fd, (struct sockaddr *)&serv_addr, sizeof(struct sockaddr_in)) < 0) {
        perror("connect");
        exit(1);
}
inet_aton()函数将一个字符串转换成一个网络地址,并把该网络地址赋给第二个参数
htons() 是字节顺序转换函数。
      

4917

主题

5879

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34382
地板
 楼主| 发表于 2009-7-10 20:15:15 | 只看该作者

绑定套接字

<span style=\"font-family: courier new,courier,monospace;\">bind() 函数用来将一个套接字和某个端口绑定到一块,bind() 函数原型如下:<br /></span><span style=\"color: rgb(0, 136, 0);\"> </span>
<span style=\"color: rgb(0, 136, 0);\">#include <sys/types&#46;h></span><br /> <span style=\"color: rgb(0, 136, 0);\">#include <sys/socket&#46;h></span><br /> <br /> <span style=\"color: rgb(0, 187, 0); font-weight: bold;\">int</span> <span style=\"color: rgb(0, 0, 0);\">bind</span>(<span style=\"color: rgb(0, 187, 0); font-weight: bold;\">int</span> <span style=\"color: rgb(0, 0, 0);\">sockfd</span><span style=\"color: rgb(0, 0, 0);\">,</span> <span style=\"color: rgb(170, 34, 255); font-weight: bold;\">struct</span> <span style=\"color: rgb(0, 0, 0);\">sockaddr</span> <span style=\"color: rgb(102, 102, 102);\">*</span><span style=\"color: rgb(0, 0, 0);\">my_addr</span><span style=\"color: rgb(0, 0, 0);\">,</span> <span style=\"color: rgb(0, 0, 0);\">socklen_t</span> <span style=\"color: rgb(0, 0, 0);\">addrlen</span>);
<br /><br /><span style=\"font-family: courier new,courier,monospace;\">socket() 只是创建了一个套接字,这个套接字将工作在哪个端口上,程序并没有指定。<br />在客户机/服务器模型中,服务器端的 IP 地址和端口号一般是固定的,因此在服务器端的程序中,使用 bind()函数将一个套接字和某个端口绑定到一起,该函数一般只有服务器端的程序调用。<br /><br /><span style=\"color: rgb(255, 0, 204);\">my_addr</span> 指定了 sockfd 将绑定到的本地地址,可以将参数 my_addr 的 sin_addr 设置为 <span style=\"font-weight: bold;\">INADDR_ANY</span> 而不是某个确定的 IP 地址,这样就可以绑定到任何网络接口。对于只有一个 IP 地址的计算机,<span style=\"font-weight: bold;\">INADDR_ANY</span> 对应的就是它的 IP 地址;对于多宿主主机(拥有多张网卡),INADDR_ANY 表示本服务器将处理来自所有网络接口上相应端口的连接请求。<br /><br />函数执行成功返回 0,有错误返回 -1,错误代码存入 errno 中。<br /><br /><span style=\"font-weight: bold;\">函数常见用法如下</span>:<br /></span><span style=\"color: rgb(170, 34, 255); font-weight: bold;\"> </span>
<span style=\"color: rgb(170, 34, 255); font-weight: bold;\">struct</span> <span style=\"color: rgb(0, 0, 0);\">sockaddr_in</span>      <span style=\"color: rgb(0, 0, 0);\">serv_addr</span><br /> <span style=\"color: rgb(0, 0, 0);\">memset</span>(<span style=\"color: rgb(102, 102, 102);\">&</span><span style=\"color: rgb(0, 0, 0);\">serv_addr</span><span style=\"color: rgb(0, 0, 0);\">,</span> <span style=\"color: rgb(102, 102, 102);\">0</span><span style=\"color: rgb(0, 0, 0);\">,</span> <span style=\"color: rgb(170, 34, 255); font-weight: bold;\">sizeof</span>(<span style=\"color: rgb(170, 34, 255); font-weight: bold;\">struct</span> <span style=\"color: rgb(0, 0, 0);\">sockaddr_in</span>));<br /> <span style=\"color: rgb(0, 0, 0);\">serv_adr</span><span style=\"color: rgb(0, 0, 0);\">&#46;</span><span style=\"color: rgb(0, 0, 0);\">sin_family</span> <span style=\"color: rgb(102, 102, 102);\">=</span> <span style=\"color: rgb(0, 0, 0);\">AF_INET</span><br /> <span style=\"color: rgb(0, 0, 0);\">serv_addr</span><span style=\"color: rgb(0, 0, 0);\">&#46;</span><span style=\"color: rgb(0, 0, 0);\">sin_port</span> <span style=\"color: rgb(102, 102, 102);\">=</span> <span style=\"color: rgb(0, 0, 0);\">htons</span>(<span style=\"color: rgb(102, 102, 102);\">80</span>);<br /> <span style=\"color: rgb(0, 0, 0);\">serv_addr</span><span style=\"color: rgb(0, 0, 0);\">&#46;</span><span style=\"color: rgb(0, 0, 0);\">sin_addr</span><span style=\"color: rgb(0, 0, 0);\">&#46;</span><span style=\"color: rgb(0, 0, 0);\">s_addr</span> <span style=\"color: rgb(102, 102, 102);\">=</span> <span style=\"color: rgb(0, 0, 0);\">htonl</span>(<span style=\"color: rgb(0, 0, 0);\">INADDR_ANY</span>);<br /> <br /> <span style=\"color: rgb(170, 34, 255); font-weight: bold;\">if</span> (<span style=\"color: rgb(0, 0, 0);\">bind</span>(<span style=\"color: rgb(0, 0, 0);\">sock_fd</span><span style=\"color: rgb(0, 0, 0);\">,</span> (<span style=\"color: rgb(170, 34, 255); font-weight: bold;\">struct</span> <span style=\"color: rgb(0, 0, 0);\">sockaddr</span> <span style=\"color: rgb(102, 102, 102);\">*</span>)<span style=\"color: rgb(102, 102, 102);\">&</span><span style=\"color: rgb(0, 0, 0);\">serv_addr</span><span style=\"color: rgb(0, 0, 0);\">,</span> <span style=\"color: rgb(170, 34, 255); font-weight: bold;\">sizeof</span>(<span style=\"color: rgb(170, 34, 255); font-weight: bold;\">struct</span> <span style=\"color: rgb(0, 0, 0);\">sockaddr_in</span>) <span style=\"color: rgb(102, 102, 102);\"><</span> <span style=\"color: rgb(102, 102, 102);\">0</span>) <span style=\"color: rgb(0, 0, 0);\">{</span><br />                 <span style=\"color: rgb(0, 0, 0);\">perror</span>(<span style=\"color: rgb(187, 68, 68);\">"bind"</span>);<br />                 <span style=\"color: rgb(0, 0, 0);\">exit</span>(<span style=\"color: rgb(102, 102, 102);\">1</span>);<br /> <span style=\"color: rgb(0, 0, 0);\">}</span>
<br />

4917

主题

5879

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34382
5#
 楼主| 发表于 2009-7-10 22:11:17 | 只看该作者

接受连接

listen() 函数把套接字转化为被动监听,其函数原型为:
#include <sys/socket.h>

int listen(int s, int backlog);
由函数 socket() 创建的套接字是主动套接字,这种套接字可以用来主动请求连接到某个服务器( 通过函数 connect() )。但是作为服务器端的程序,通常在某个端口上监听来自客户端的连接请求。

在服务器端,一般是先调用函数 socket() 创建一个主动套接字,然后用函数 bind() 将该套接字绑定到某个端口上,接着再调用函数 listen() 将该套接字转化为监听套接字,等待来自客户端的连接请求。

一般多个客户端连接到一个服务器,服务器向这些客户端提供某种服务。服务器端设置一个连接队列,记录已经建立的连接,参数 backlog 指定了该队列的最大长度。如果连接队列已经到达最大,之后的连接请求将被服务器拒绝。

函数执行成功后返回 0,出错返回 -1,错误代码存入 errno 中。

注意:函数 listen() 只是将套接字设置为倾听模式以等待连接请求,它并不能接收连接请求,真正接收客户端连接请求的是 accept() 。

该函数常见用法
#define  LISTEN_NUM  12   //定义连接请求队列长度
...
if (listen(sock_fd, LISTEN_NUM) < 0 {
        perror("listen");
        exit(1);
}

4917

主题

5879

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34382
6#
 楼主| 发表于 2009-7-10 23:28:25 | 只看该作者

接受连接

accept() 用来接受一个连接请求,函数原型如下:
#include <sys/types.h>
#include <sys/socket.h>

int accept(int s, struct sockaddr *addr, socklen_t *addrlen);
参数 s 是由函数 socket() 创建,经函数 bind() 绑定到本地某一端口上,然后通过函数 listen() 转化而来的监听套接字。

addr :
用来保存发起连接请求的主机的地址和端口。

addrlen :
此参数是 addr 所指向的结构体的大小。

函数执行成功后返回一个新的代表客户端的套接字,出错则返回 -1,错误代码存入 errno 中。

只能对面向连接的套接字使用 accept() 函数。 accept() 执行成功时,将创建一个新的套接字,并且为这个新的套接字分配一个套接字描述符,并返回这个新的套接字描述符。这个新的套接字描述符与打开文件时返回的文件描述符类似,进程可以利用这个新的套接字描述符与客户端交换数据,参数 s 所指定的套接字继续等待客户端的连接请求。

如果参数 s 所指定的套接字被设置为阻塞方式 (Linux 下默认的方式),且连接请求队列为空,则 accept() 将被阻塞直到有连接请求到达为止。
如果参数 s 所指定的套接字被设置为非阻塞方式 ( fcntl() 函数设置 ),则如果队列为空,accept() 将立即返回 -1,errno 被设置为 EAGAIN 。

套接字为阻塞方式下该函数的常见用法
int  client_fd;
int  client_len;
struct sockaddr_in     client_addr;
...
client_len = sizeof (struct sockaddr_in);
client_fd = accept (sock_fd, (struct sockaddr *)&client_addr, &client_len);
if (conn_fd < 0) {
    perror("accept");
    exit(1);
}

服务器与客户端

4917

主题

5879

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34382
7#
 楼主| 发表于 2009-7-11 00:13:22 | 只看该作者

TCP 套接字的数据传送

1、发送数据

send() 函数用来在 TCP 上发送数据,其原型为:
#include <sys/types.h>
#include <sys/socket.h>

int send(int s, const void *msg, size_t len, int flags);
 send() 只能对处于连接状态的套接字使用。
s       参数为已建立好连接的套接字描述符,即 accept() 函数的返回值。
msg 参数指向存放待发送数据的缓冲区。
len    参数为待发送数据的长度。

参数 flags 为控制选项,一般设置为 0 或取以下值:
      MSG_OOB  : 在指定的套接字上发送带外数据 ( out-of-band data ),该类型的套接字必须支持带外数据 ( 如 SOCK_STREAM )。
      MSG_DONTROUTE : 通过最直接的路径发送数据,而忽略下层协议的路由设置。
如果要发送的数据太长而不能发送时,将出现错误,errno 设置为 EMSGSIZE;
如果要发送的数据长度大于该套接字的缓冲区剩余空间大小时,send() 一般会被阻塞,如果该套接字被设置为非阻塞方式,则此时立即返回 -1 并将 errno 设为 EAGAIN 。

函数执行成功返回实际发送数据的字节数,出错返回 -1。

注意,执行成功只是说明发送的数据写入套接字的缓冲区中,并不表示数据已经成功地通过网络发送到目的地。

套接字为阻塞的方式下,函数的常见用法
#define   BUFFERSIZE    1500
char   send_buf[BUFFERSIZE];
...
if (conn_fd, send_buf, len, 0) {         // len 为待发送数据的长度
     perror("send");
     exit(1);
}

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|曲径通幽 ( 琼ICP备11001422号-1|公安备案:46900502000207 )

GMT+8, 2024-4-28 15:00 , Processed in 0.090538 second(s), 21 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表