《Linux程序设计》套接字笔记

时间:2022-02-16 10:19:37

套接字:一種通信機制,通過套接字可以進行本地和網絡的鏈接。明確的講客戶和服務器區分開來(cs架構),是系統分配給服務器進程的類似與文件描述符的資源,不能與其他進程共享。
本地的名字是Linux的文件名,一般放在/tmp(/usr/tmp)
網絡套接字是與客戶鏈接的特定網絡有關的服務器標示符(端口號或者訪問點)
socket通信的流程如下:
《Linux程序设计》套接字笔记
實例:
問題:
本地通信出現找不到文件: No such file or directory
在ubuntu下面沒有權限,需要改變路徑爲/tmp/ssss.domain 就OK了

一個簡單的本地客戶:

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <sys/un.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
int main()
{
int sockfd = 0;
int len = 0;
struct sockaddr_un address;
int result = 0;
char ch = 'A';

extern int errno;

sockfd = socket(AF_UNIX,SOCK_STREAM,0);
address.sun_family = AF_UNIX;
strcpy(address.sun_path,"/tmp/server_socket.domain");
len = sizeof(address);

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

if ( result == -1)
{
perror("oops client1\n");
printf("errno = %d\n",errno);
exit(1);
}

write(sockfd,&ch,1);
read(sockfd,&ch,1);
printf("char from server = %c\n",ch);
close(sockfd);
exit(0);
}

一個簡單的本地服務器:

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <sys/un.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
int main()
{
int server_sockfd = 0;
int client_sockfd = 0;
int server_len = 0;
int client_len = 0;

extern int errno;

struct sockaddr_un server_address;
struct sockaddr_un client_address;
// create the local file
unlink("/tmp/server_socket.domain");
server_sockfd = socket(AF_UNIX,SOCK_STREAM,0);
// the way of comunicating is local
server_address.sun_family = AF_UNIX;
//the location
strcpy(server_address.sun_path,"/tmp/server_socket.domain");
server_len = sizeof(server_address);

bind(server_sockfd,(struct sockaddr*)&server_address,server_len);
listen(server_sockfd,1);
while ( 1 )
{
char ch;
printf("server waiting\n");
client_len = sizeof(client_address);
client_sockfd = accept(server_sockfd,(struct sockaddr*)&client_address,(socklen_t*)&client_len);
//hanle the error
if ( client_sockfd == -1 )
{
printf("errno = %d\n",errno);
close(server_sockfd);
exit(1);
}
read(client_sockfd,&ch,1);
ch++;
write(client_sockfd,&ch,1);
close(client_sockfd);
}
}

套接字屬性:
1 域:指定套接字使用的網絡介質
比如:AF_INET,AF_INET6,AF_UNIX,AF_ISO,AF_XNS
2 類型
流(stream)
流套接字由類型sock_stream指定,在AF_INET是使用TCP/IP實現的,具有有序,可靠,雙向字節流,在傳輸的過程中遇到錯誤不會返回,太大的數據將會從組,很像文件流
數據報(datagram)
由類型SOCK_DGRAM指定,在AF_INET是由UDP/IP實現的
優點 服務器奔潰了,也不會影響上課
3 協議
底層的傳輸機制可以使用不止一個協議來提供要求的套接字類型,就可以選擇一個特定的協議。
todo:有什麼協議
unix網絡套接字和文件系統套接字,只需要使用默認的就可以
4 創建套接字API

#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain,int type,int protocol)

domain制定協議族,包括:
AF_UNIX UNIX域協議(文件系統套接字) 常用 本地套接字
AF_INET APRA因特網協議(UNIX網絡套接字) 常用 包括因特網在內的TCP/IP網絡進行通信的程序
AF_ISO ISO標準協議
AF_NS 施樂網絡系統協議
AF_IPX Novell IPX協議
AF_APPLETALK Appletalk DDS
type:
SOCK_STREAM :字節流,有序,可靠,面向鏈接,雙向,AF_INET默認通過TCP提供,TCP提供分片和重組長消息,重傳丟失的數據
SOCK_DGRAM:數據報,不可靠,亂序,AF_INET由UDP數據報提供
protocol
協議通常由套接字類型和套接字域決定,通常不需要選擇,默認爲0
返回值:
整形的描述符,可以通過該描述符接受和發送數據
5 套接字地址
AF_UNIX:由sockaddr_un描述

#include <sys/un.h>
struct sockaddr_un{
sa_family_t sum_family;
char sun_path[];
}

對套接字處理的不同系統調用可能使用不同類型但類似的結構的地址結構來描述
sum_family :指定地址類型
sun_path:套接字地址
AF_INET:由sockaddr_in來描述

#include <netinet/in.h>
struct sockaddr_in{
short int sin_family;
unsigned short int sin_port;
struct in_addr sin_addr;
}
struct in_addr{
unsigned long int s_addr;
}

AF_INET由域,IP地址和端口號來完全確定,從應用角度來看所以的套接字行爲就像文件描述符一樣,並且通過一個唯一的整數值來區分。
6 命名套接字
通過socket創建的套接字必須命名才能被其他進程使用。
bind系統調用吧address中的地址分配給與文件描述付socket關聯的命名套接字,
address_len地址結構的長度

#include <sys/socket.h>
int bind(int socket,const struct sockaddr* address,size_t address_len)

返回:
0 成功
-1 失敗
errno值
EBADF 文件描述付無效
ENOTSOCK 文件描述付對應的不是一個套接字
EINVAL 文件描述付對應的是一個已命名的套接字
EADDRNOTAVAIL 地址不可用
EADDRINUSE 地址已經綁定了一個套接字
EACCESS 權限不足
ENOTDI 文件名不符合要求
7創建套接字隊列
通過listen系統調用來完成,服務器程序必須調用

#include <sys/socket.h>
int listen(int socket,int backlog);

backlog:等待處理的連接數最大個數,往後的鏈接講失敗,常用5
返回值
0 成功
-1 失敗
8 接受鏈接
通過accept系統調用

#include <sys/socket.h>
int accept(int socket,struct sockaddr* address)

accept接受套接字對壘未處理的第一個鏈接,返回新的套接字描述符,新套接字描述符與服務器監聽的類型是一樣的
套接字必須先命名(bind),並且由listen調研給他分配一個連接隊列。address_len預期的地址長度,大於則系統會
進行截斷,不關心地址的情況下addres可以爲空
阻塞:
1 套接字隊列沒有未處理的鏈接
可以通過O_NONBLOCK改變這一行爲
int flags = fcntl(socket,F_GETFL,0)
返回-1 發生錯誤
EWOULDBLOCK :指定了O_NONBLOCK,隊列中沒有未處理的鏈接
EINTR:阻塞時。執行被中斷
9 請求鏈接
通過connect系統調用

#include <sys/socket.h>
int connect(int socket,const struct sockaddr* address,size_t address_len)

socket指定的套接字講鏈接到address指定的服務器套接字,地址長度由address_len指定。
返回
0 成功
-1 失敗
EBADF 文件描述付無效
EALREADY 該套接字上已經有一個在進行鏈接
ETIMEDOUT 鏈接超時
ECONNREFUSED 鏈接請求被服務器拒絕
鏈接不能立刻建立,講阻塞一段不確定的時間,時間到達,鏈接放棄,調用失敗
但如果是被信號中斷,該型號得到處理,調用失敗(errno設置爲EINTR),鏈接會以異步方式建立
必須檢查該鏈接是否成功建立
可以通過O_NONBLOCK改變這一行爲
int flags = fcntl(socket,F_GETFL,0)
不能立刻建立,調用失敗(errno設置爲EINPROGRESS),鏈接將以異步方式進行,可以使用select來檢查。
10 關閉套接字
通過close系統調用
11 套接字通信
文件套接字缺點:除非定義爲絕對路徑名,否則套接字創建在服務器的當前目錄下,要更通用型,必須放在客戶端和服務器
都認可的全局訪問的目錄,網絡套接字使用端口號就可以了
每个与计算机通信的网络都有一个对应的硬件接口,一个计算机在每个网络中都可能有不同的名字,记录中/etc/hosts文件中,比如:
127. 0. 0. 1 localhost # Loopback
192. 168. 1. 1 tilde. localnet # Local, private Ethernet
158. 152. X. X tilde. demon. co. uk # Modem dial- up
12 主机字节序与网络子节序
主机字节序列分为大端和小端,比如:0x12345678
在ibm powerpc这种大端的机器中用0x12345678 表示
在itel pc x86中用0x87654321表示
为了在不同系列的计算机中,通过网络传输数据的时候达成一致,使用了网络子节序
从计算机传到网络的时候,先转换为网络子节序
从网络读取数据到计算机的时候,从网络子节序转换为计算机自己的子节序
需要包含头文件 netline/in.h

#include <netline/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位整数中计算机子节序和网络子节序中转换
函数名是与之操作的简写形式
比如htonl(host to network long)
htons(host to network short)
13 网络信息
对于一个更通用的网络服务器和客户端来讲,可以通过网络信息函数来决定使用的地址和端口号,主机数据库的函数在

struct hostent *gethostbyaddr( const void *addr, size_ t len, int type); 
struct hostent *gethostbyname( const char *name);

返回值最少返回几个成员

struct hostent { char *h_ name; /* name of the host */
char **h_ aliases; /* list of aliases (nicknames) */
int h_ addrtype; /* address type */
int h_ length; /* length in bytes of the address */
char **h_ addr_ list /* list of address (network order)

如果没有则返回空指针
与服务及其端口号有关的信息也可以通过一些服务信息来获取

#include < netdb. h>
struct servent *getservbyname( const char *name, const char *proto);
struct servent *getservbyport( int port, const char *proto);

proto 参数表示链接该服务的协议,有两个取值tcp(SOCK_STREAM)和udp(SOCK_DGRAM)
返回值最少包括以下几个成员

struct servent { 
char *s_ name; /* name of the service*/
char **s_ aliases; /* list of aliases (alternative names) */
int s_ port; /* The IP port number */
char *s_ proto; /* The service type, usually "tcp" or "udp" */
};

可以调用gethostbyname把结果打印出来,但要调用inet_ ntoa转换为可以打印的字符串

#include < arpa/ inet. h> 
char *inet_ ntoa( struct in_ addr in)

这个函数的作用是把一个主机因特网地址转换为一个点分四元祖的字符串
失败时返回-1
还可以用以下的函数

#include < unistd. h> 
int gethostname( char *name, int namelength);

它的作用是把主机名字写入name中,namelength表示长度,太长则会截断,失败返回-1
14 守护进程xinetd/inetd
超级服务器程序(因特网守护进程)同时监听许多端口地址上的链接,当有客户链接上时,超级服务程序就会启动相应的服务。这就让针对各项网络服务不需要一直运行着。
xinetd/inetd通常使用相应的界面来配置也可以通过配置文件来设置
15 套接字选项
套接字选项很多,可以通过以下函数来设置

#include < sys/ socket. h> 
int setsockopt( int socket, int level, int option_ name, const void *option_ value, size_ t option_ len);

成功返回0,失败返回-1
16 多客户
一旦连接建立,套接字的行为就类似于底层的文件描述符,在很多情况下类似于双向通道。服务器在接受一个新连接的时候会创建出一个新套接字,原先的套接字会继续监听以后的连接,放在队列里等待处理,原先的套接字的行为就想文件描述符。给了我们一种方法,我们可以调用fork产生子进程,子进程继承打开的套接字,子进程可以和客户通信,主进程接受以后的新连接。主要要设置SIGCHID的信号处理函数避免出现僵尸进程。这样就可以实现一个服务器处理多个客户端。
17 select
16提到的方法不是最佳的方法,因为我们需要的是一种不阻塞,不等待客户请求到达的情况下实现处理多个客户。
select系统调用可以同时在多个底层文件描述符上等待输入的到达。

#include < sys/ types. h>
#include < sys/ time. h>
void FD_ ZERO( fd_ set *fdset); //用于将fdset初始化情况
void FD_ CLR( int fd, fd_ set *fdset); //清除集合中有fd指定的文件描述符
void FD_ SET( int fd, fd_ set *fdset); //设置集合中由fd指定的文件描述符
int FD_ ISSET( int fd, fd_ set *fdset); //当fd传递的文件描述符是集合fdset的元素时,返回非0值
select可以使用timevale设置一个超时值防止出现无线阻塞。
struct timeval { time_ t tv_ sec; /* seconds */
long tv_ usec; /* microseconds */
}

select 函数 会在 发生 以下 情况 时 返回: readfds 集合 中有 描述 符 可读、 writefds 集合 中有 描述 符 可 写 或 errorfds 集合 中有 描述 符 遇到 错误 条件。 如果 这 3 种 情况 都没 有 发生, select 将 在 timeout 指定 的 超时 时间 经过 后 返回。 如果 timeout 参数 是 一个 空 指针并且 套 接 字 上 也没 有 任何 活动, 这个 调用 将 一直 阻塞 下去。
18 数据报服务
当客户发送一个短小的请求给服务器,并且期望接受到一个短小的响应时,使用数据报(UDP)服务就可以了。相比于字节流(TCP),数据报简化了服务器编程的难度。
因为 UDP 提供 的 是 不可靠 服务, 所以 你 可能 发现 数 据报 或 响应 会 丢失。 如果 数据 对于 你 来说 非常 重要, 就 需要 小心 编写 UDP 客户 程序, 以 检查 错误 并在 必要 时 重传。 实际上, UDP 数 据报 在 局域 网中 是非 常 可靠 的。
你 需要 像 以前 一样 使用 套 接 字 和 close 系统 调用, 但 你 需要 用两 个数 据报 专用 的 系统 调用 sendto 和 recvfrom 来 代替 原来 使 用在 套 接 字 上 的 read 和 write 调用。