您的位置:首页 > 运维架构 > Linux

Linux程序设计--套接字学习笔记

2016-03-02 17:17 423 查看

1,什么是套接字

 

套接字(socket)是一种通信机制,凭借这种机制,客户/服务器系统的开发工作既可以在本地单机上进行,也可以跨网络进行。例如连接数据库,提供Web页面,远程登陆等。

2,套接字的属性

套接字的特性由3个属性决定:域(domain),类型(type)和协议(protocol)。

2.1 套接字的域

套接字的域指定通信中使用的网络介质。最常见的套接字域是AF_INET,它指的是Internet网络,其底层的协议是--网际协议(IP)。客户可以通过IP地址和端口号来指定一台联网机器上的特定服务。

常见的套接字域:

说明
AF_UNIXUNIX域协议(文本系统套接字
AF_INETARPA因特网协议(UNIX网络套接字
AF_ISOISO标准协议
AF_NS施乐(Xerox)网络系统协议
AF_IPXNovell IPX协议
AF_APPLEEALKAppletalk DDS

2.2 套接字类型

  一个套接字域可能有多种不同的通讯方式,每种通信方式又有其不同的特性。

   

  因特网协议提供了两种通信机制:流(stream)和数据报(datagram)。

   

  流套接字提供的是一个有序,可靠,双向字节流的连接。流套接字由类型SOCK_STREAM指定,它们是在AF_INET域中通过TCP/IP连接实现的。

   

  数据报套接字与流套接字相反,它提供的是一种无序的不可靠服务,由类型SOCK_DGRAM指定,它不建立和维持一个连接。数据报作为一个单独的网络消息被传输,它可能丢失,复制或乱序到达。在AF_INET域中通过UDP/IP连接实现。

   

   

2.3 套接字协议

只要底层的传输机制允许不止一个协议来提供要求的套接字类型,我们就可以为套接字选择一个特定的协议。常用的协议有,IPPROTO_TCP、IPPROTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。注意并不是套接字类型和协议可以随便组合,当协议(protocol)参数为0时,会根据域(domain)和类型(type)选择对应的默认协议。

3 简单的客户端和服务器端编写流程

客户端:

   1,为客户创建一个套接字(socket)

   2,根据服务器情况配置端口和IP地址(sockaddr_in, sockaddr_un)

   3,请求连接到服务器(connect)

   4,接收信息或发送信息(read, write, send, recv)

   5,关闭套接字(close)

   

服务器端:

   1,为服务器创建一个套接字(socket)

   2,配置端口和IP地址(sockaddr_in, sockaddr_un)

   3,命名套接字,关联到特定的地址和端口(bind)

   4,创建套接字队列,监听服务器请求(listen)

   5,接受连接(accept)

   6,接收信息或发送信息(read, write, send, recv)

   7,关闭套接字(close)

   



4 具体函数描述

4.1 创建套接字

  通过socket函数调用创建一个套接字并返回一个描述符,该描述符可以用来访问该套接字。

   #include <sys/types.h>

   #include <sys/socket.h>

   

   int socket(int domain, int type, int protocol);

  创建的套接字是一条通信线路的一个端点,客户端套接字和服务器端套接字是一条通信线路的两个端点。domain参数指定协议族,也就是前面讲的域,type参数指定这个套接字的通信类型(流或数据报等),protocol参数指定使用的协议。套接字的属性就是由这三个参数来指定的。

4.2 套接字地址

每个套接字域都有自己的地址格式。对于AF_UNIX域套接字来说,它的地址由结构sockaddr_un来描述,该结构定义在头文件sys/un.h中。

  struct sockaddr_un {

      sa_family_t    sun_family;     /*AF_UNIX */

      char           sun_path[];     /*pathname */

};


  在AF_INET域中,套接字地址由结构sockaddr_in来指定,该结构定义在头文件netinet/in.h中,它至少包含以下几个成员:

  

  struct sockaddr_in {

      short int            sin_family;        /* AF_INET */

      unsigned short int   sin_port;          /* Port number */

      struct in_addr      sin_addr;         /* Internet address */

  };


  IP地址结构in_addr被定义为:

  

  struct in_addr {

      unsigned long int    s_addr;

  };


  IP地址中的4个字节组成一个32位的值。一个AF_INET套接字由它的域,IP地址和端口号来完全确定。

  

  

4.3 命名套接字

  要想让通过socket调用创建的套接字可以被其他进程使用,服务器程序就必须给该套接字命名。这样,AF_UNIX套接字就会关联到一个文件系统的路径名。AF_INET套接字就会关联到一个IP端口号。

  

  #include <sys/socket.h>

  

  int bind(int socket, const struct sockaddr *address, size_t address_len);


  Bind系统调用把参数address中的地址分配给与文件描述符socket关联的未命名套接字。地址结构的长度由参数address_len传递。

  

  地址的长度和格式取决于地址族。Bind调用需要将一个特定的地址结构指针转换为指向通用地址类型(struct sockaddr *)。

  

  Bind调用成功时返回0,失败时返回-1并设置errno值。

  

  

4.4 创建套接字队列

  为了能够在套接字上接受进入的连接,服务器程序必须创建一个队列来保存未处理的请求。它用listen系统调用来完成这一工作。

  

  #include <sys/socket.h>

  

  int listen(int socket, int backlog);

  

  参数backlog为队列可以容纳未处理连接的最大数目,若等待处理的连接个数超过这个值,其后的连接将被拒绝。Listen函数在成功时返回0,失败时返回-1。

  

4.5 接受连接

  当服务器程序创建并命名了套接字之后,它就可以通过accept系统调用来等待客户建立对该套接字的连接。

  

  #include <sys/socket>

  

  int accept(int socket, struct sockaddr *address, size_t *address_len);
 
  Accept系统调用只有当有客户程序试图连接到由socket参数指定的套接字上时才返回。Aceept函数将创建一个新的套接字来与该客户进行通信,并且返回新套接字的描述符。

  

  参数socket必须事先由bind调用命名,并且由listen调用给它分配一个连接队列。连接客户的地址将被放入address参数指定的sockaddr结构中。参数address_len指定客户结构的长度,如果客户地址的长度超过这个值,它将被截断。当这个调用返回时,address_len将被设置为连接客户地址结构的实际长度。

  

  如果套接字队列中没有未处理的连接,accept将阻塞(程序将暂停)直到有客户连接为止。当然你也可以使用函数fcntl改变这一行为。

  

  

4.6 请求连接(客户端)

  客户程序通过一个未命名的套接字和服务器监听套接字之间建立连接的方法来连接到服务器。它们通过connect调用来完成这一工作。

  

  #include <sys/socket.h>

  

  int connect(int socket, const struct sockaddr *address, size_t address_len)


  

  参数socket指定的套接字将连接到参数address指定的服务器套接字,address指向的结构的长度由参数address_len指定。

  

  成功时,connect调用返回0,失败时返回-1。

  

  

  

4.7 接收和发送信息

  

我们可以通过read/write,recv/send 等函数来接收和发送数据。

  

#include <unistd.h>

ssize_t read(int fd, void *buf, size_t count);

ssize_t write(int fd, const void *buf, size_t count);


  参数fd是我们通过socket函数调用创建套接字时返回的描述符,read函数是读取fd对应的套接字中的数据到buf中,write函数是想套接字中写入buf中的数据。Count参数是控制数据的大小。

  

当read函数读取成功时,将返回实际读取的字节数,如果返回的值是0表示已经读到文件的结束了,小于0表示出现了错误。同样对于write函数写入成功时,将返回写入的字节数,失败时返回-1。

4.8 关闭套接字

通过调用close函数来终止服务器和客户上的套接字。你应该总是在连接的两端都关闭套接字。

5 简单实例

客户端: client.c

#include <sys/types.h>

#include <sys/socket.h>

#include <stdio.h>

#include <netinet/in.h>

#include <arpa/inet.h>

#include <unistd.h>

#include <stdlib.h>

int main()  {

int sockfd;

int len;

struct sockaddr_in address;

int result;

char ch = 'A';

//为客户建立一个套接字

sockfd = socket(AF_INET, SOCK_STREAM, 0);

//定义sockaddr_in,配置套接字地址,与服务器保持一致,

address.sin_family = AF_INET;

address.sin_addr.s_addr = inet_addr("127.0.0.1");

address.sin_port = htons(9734);

len = sizeof(address);

//请求连接服务器

result = connect(sockfd, (struct sockaddr *)&address, len);

//若连接失败,打印失败信息,并退出程序

if(result == -1) {

perror("oops: client1");

exit(0);

}

//向客户套接字中写入数据,内容是 'A',字节数为1

write(sockfd, &ch, 1);

//读取客户套接字中的一个字节的数据

read(sockfd, &ch, 1);

printf("char from server = %c\n", ch);

//关闭套接字

close(sockfd);

exit(0);

}


服务器端: server.c

#include <sys/types.h>

#include <sys/socket.h>

#include <stdio.h>

#include <netinet/in.h>

#include <arpa/inet.h>

#include <unistd.h>

#include <stdlib.h>

int main() {

int server_sockfd, client_sockfd;

int server_len, client_len;

struct sockaddr_in server_address;

struct sockaddr_in client_address;

//为服务器创建一个未命名的套接子:

server_sockfd = socket(AF_INET, SOCK_STREAM, 0);

//配置套接字地址

server_address.sin_family = AF_INET;

server_address.sin_addr.s_addr = inet_addr("127.0.0.1");

server_address.sin_port = htons(9734);

server_len = sizeof(server_address);

//命名套接字,关联到特定的地址和IP端口

bind(server_sockfd, (struct sockaddr *)&server_address, server_len);

//创建一个链接队列,开始等待客户进行连接:

listen(server_sockfd, 5);

while(1) {

char ch;

printf("server waitting\n");

//接受一个连接

client_len = sizeof(client_address);

client_sockfd = accept(server_sockfd,

(struct sockaddr *)&client_address, &client_len);

//对client_sockfd套接子上的客户进行读写操作

read(client_sockfd, &ch, 1);

ch++;

write(client_sockfd, &ch, 1);

//关闭套接字

close(client_sockfd);

}

}


  在终端上运行命令:

  gcc client.c -o client

  gcc server.c -o server

  

  ./server

  ./client


  输出:char from server = B

  在上面的例子中的inet_addr("127.0.0.1")语句,意思是用inet_addr函数将IP地址的文本表示方式转换为符合套接字地址要求的格式。还有一个地方需要注意的是语句htons(9734),这是把主机字节序转换成了网络字节序。相关的内容下节将会说明。

  

  

  

6 主机字节序和网络字节序

  

在不同的计算机中,会使用不同的字节序来表示整数。例如,intel处理器将32位的整数分为4个连续的字节,并以字节序1-2-3-4存储到内存中,这里的1表示高位字节,在内存中存储在起始地址,这种字节序称为大端(big-endian)字节序。而IBM PowerPC处理器是以字节序4-3-2-1的方式存储,低位字节4存储在起始地址,这种字节序称为小端(little-endian)字节序。

  

   我们把某个给定系统所用的字节序称为主机字节序(host byte order)。为了使不同的计算机可以就通过网络传输的多字节整数的值达成一致,你需要定义一个网络字节序(network byte order)。网络字节序使用大端字节序来传送这些多字节整数。客户和服务器程序必须在传输之前,将它们的内部整数表示方式转换为网络字节序。它们通过在头文件netinet/in.h中的函数来完成这一工作。

  #include <netinet/in.h>

  

  unsigned long int htonl(unsigned long int hostlong);

  unsigned short int htons(unsigned short int hostshort);

  unsigned long int ntohl(unsigned long int netlong);

  unsigned short int ntohs(unsigned short int netshort);


  

这些函数将16位和32位整数在主机字节序和标准的网络字节序之间进行转换。函数名是与之对应的的转换操作的简写形式。例如“host to network,long”(htonl, 长整数从主机字节序到网络字节序的转换)和”host to network, short”(htons, 短整数从主机字节序到网络字节序的转换)。

参考:

1, Linux程序设计

2,UNIX网络编程卷一
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: