linux/unix编程----socket

75 阅读6分钟

通信domain

socket存在于一个通信domain中,它确定:

识别出一个socket的方法(即socket“地址”格式)

  • 通信范围(同一主机的应用程序?通过网络连接的应用程序)
  • UNIX(AF_UNIX)domain允许统一主机上的应用程序之间进行通信
  • IPv4(AF_INET)domain允许使用IPv4网络连接的应用程序之间进行通信
  • IPv6(AF_INET6)domain允许使用IPv6网络连接的应用程序之间进行通信

socket domain

Doamin		执行的通信		应用程序间的通信		地址模式				地址结构
AF_UNIX 	内核中			同一主机 				路径名					sockaddr_un
AF_INET 	IPv4 			通过ipv4连接的主机 		32位ipv4+16位端口		sockaddr_in
AF_INET6 	IPv6 			通过ipv6连接的主机		128位ipv6+16位端口 		sockaddr_in6 

socket类型:流和数据报

流:SOCK_STREAM: TCP

数据报:SOCK_DGRAM: UDP

socket系统调用

  • socket()创建一个新的socket
  • bind():将调用一个socket绑定到地址上
  • listen(): 允许一个流socket接受来自其他socket的接入请求
  • connect(): 建立与另一个socket之间的连接
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
//成功返回对应文件描述符,失败返回-1
int bind(int sockfd, const struct sockaddr*addr, socklen_t addrlen);
//成功返回0,失败返回-1 
//addr参数是一个指针,指向一个指定该socet绑定到的地址的结构

socket地址结构:strcut sockaddr

对于各种socket domain都需要定义一个不同的结构类型类存储socket的地址。然后由于诸如bind()之类的系统调用适用于所有的socket domain,因此他们必须要能够接受任意类型的地址结构。为了支持这种行为,socket API定义了一个通用的地质结构struct sockaddr.

这个类型的唯一用途是将各种socket domain特定的地址结构转换成单个类型以供socket系统调用中的各个参数使用

struct sockaddr{
	sa_family_t sa_family;	//address family (AF_*)
	char sa_data[14];		//socket address 
}

流socket

系统调用:

listen()系统调用将文件描述符sockfd引用的流socket标记为被动,这个socket后面会被用来接受其他主动的socket连接

int listen(int sockfd, int backlog);

backlog参数:客户端可能会在服务器调用accept()之前调用connect().这将会产生一个未决的连接。内核必须要记录所有未决的连接请求信息,这样后续的accept()就能处理这些请求。

backlog参数允许限制这种未决连接的数量

accept()系统调用在文件描述符sockfd引用的监听流socket上接受一个接入请求。如果在accept()时不存在未决的连接,那么调用就会阻塞直到有连接请求到达为止

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
//成功返回文件描述符,失败返回-1
//addr参数是指向一个用来返回socket地址的结构,如果不关心对等socket地址,可以设置为NULL

accpet()会创建一个新的socket,并且正是这个新的socket会与执行connect()的对等socket进行连接。accept()调用的返回的函数结果是已连接的socket文件描述符

connect()系统调用将文件描述符sockfd引用的主动socket连接到通过地址addr指定的监听socket上

int connect(int sockfd, const struct socckaddr*addr, socklen_t addrlen);

流socket I/O

一对连接的流socket在两个端点之间提供了一个双向通信信道

要执行I/O需要使用read()和write()系统调用。由于socket是双向的,因此在连接的两端都可以使用这两个调用

连接终止close()

数据报socket

  1. 所有需要发送和接收数据报的应用程序都需要使用socket()创建一个数据报socket
  2. 服务器将socket绑定到一个众所周知的地址上,客户端会通过向该地址发送一个数据报来发起通信
  3. 使用sendto()发送数据,其中一个参数是数据报发送到socket的地址
  4. 使用recvfrom()接收数据,它在没有数据包到达时会阻塞。由于recvfrom()允许获取发送者地址,因此可以在需要时发送一个响应
ssize_t recvfrom(int sockfd, void*buffer, size_t length, int flags, struct sockaddr* *src_addr, socklen_t *addrlen);
//src_addr会返回用来发送数据报的远程socket地址
ssize_t sendto(int sockfd, const void* buffer, size_t length, int flags, const struct sockaddr* dest_addr, socklen_t *addrlen); 
//dest_addr指定数据报发送到的socket

socket: unix domain

在unix domain中,socket地址以路径名来表示

struct sockaddr_un {
	sa_family_t sun_family;	//always AF_UNIX 
	char sun_path[108];
};

为了将unix domainsocket绑定带到一个地址上,需要初始化一个sockaddr_un结构,然后将指向这个结构的一个(转换)指针作为addr参数传入bind()并将addrlen指定为这个结构的大小

//绑定一个unix domain socket 
const char *SOCKNAME="/tmp/mysock";
int sfd;
struct sockaddr_un addr;
sfd = socket(AF_UNIX, SOCK_STREAM, 0);
if(sfd==-1)
	errExit("socket);
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, SOCKNAME, sizeof(addr.sun_path)-1);
if(bind(sfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_un))==-1)
	errExit("bind");

当绑定UNIX domain socket时,bind()会在文件系统中创建一个条目。文件的所有权将根据常规的文件创建规则来确定。这个文件会被标记为一个socket,当在这个路径上应用stat()时,它会在stat结构的st_mode字段中的文件类型部分返回值S_IFSOCK

//unix domain中的流socket 
//us_xfr.h 
#include <sys/un.h>
#include <sys/socket.h>
#include <unistd.h>
#include <stdio.h>

#define SV_SOCK_PATH "/tmp/us_xfs"
#define BUF_SIZE 100
//us_xfr_sv.c 
#include "us_xfr.h"
#define BACKLOG 5 

int main(int argc, char*argv[]){
	struct sockaddr_un addr;
	int sfd, cfd;
	ssize_t numRead;
	char buf[BUF_SIZE];
	sfd = socket(AF_UNIX, SOCK_STREAM, 0);
	if(sfd==-1){
		printf("socket error. \n");
		return -1;
	}
	if(remove(SV_SOCK_PATH)==-1){
		printf("remove.\n");
		return -1;
	}
	
	memset(&addr, 0, sizeof(struct sockaddr_un));
	addr.sun_family = AF_UNIX;
	strncpy(addr.sun_path, SV_SOCK_PATH, sizeof(addr.sun_path)-1);
	if(bind(sfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_un))==-1){
		printf("bind \n");
		return -1;
	}
	if(listen(sfd, BACKLOG)==-1){
		printf("listen.\n");
		return -1;
	}
	
	for(;;){
		cfd = accept(sfd, NULL, NULL);
		if(cfd==-1){
			printf("accept.\n");
			return -1;
		}
		
		while((numRead=read(cfd, buf, BUF_SIZE))>0){
			if(write(STDOUT_FILENO, buf, numRead)!=numRead){
				printf("write.\n");
				return -1;
			}
		}
		if(numRead==-1){
			printf("read.\n");
			return -1;
		}
		if(close(cfd)==-1){
			printf("closed.\n");
			return -1;
		}
	}
	return 0;
}
//us_xfr_cl.c 
#include "us_xfr.h"

int main(int argc, char*argv[]){
	struct sockaddr_un addr;
	int sfd;
	ssize_t numRead;
	char buf[BUF_SIZE];
	
	sfd = socket(AF_UNIX, SOCK_STREAM, 0);
	if(sfd==-1){
		printf("socket.\n");
		return 0;
	}
	
	memset(&addr, 0, sizeof(struct sockaddr_un));
	addr.sun_family = AF_UNIX;
	strncpy(addr.sun_path, SV_SOCK_PATH, sizeof(addr.sun_path)-1);
	
	if(connect(sfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_un))==-1){
		printf("connect.\n");
		return -1;
	}
	
	while((numRead=read(STDIN_FILENO, buf, BUF_SIZE))>0){
		if(write(sfd, buf, numRead)!=numRead){
			printf("write.\n");
			return -1;
		}
	}
	if(numRead==-1){
		printf("read\n");
		return -1;
	}
	return 0;
}

SOCKET: internet domain

internet domain socket

internet domain 流socket是基于TCP之上的,他们提供了可靠的双向字节流通信信道。internet domain数据报socket是基于UDP之上的

在一个UNIX domain数据报socket上发送数据会在接收socket的数据队列为满时阻塞。与之不同的是,使用UDP时如果进入的数据报会使接收者的队列移除,那么数据报就会被静默的丢弃