unix套接字编程

unix套接字编程


这是学习[1]Unix网络编程第一卷的一点笔记。

在这里插入图片描述

​ 图1 基本TCP客户——服务器程序的流程图

首先启动服务器,稍后的某个时刻启动客户端,它要连接到服务器上。假设客户端给服务器发送了一个请求,服务器处理这个请求,并给客户端发回一个响应。这个过程一直持续下去,直到客户端给服务器发送一个文件结束符,并关闭客户端连接,接着服务器也关闭服务器端的连接,或结束运行或等待一个新的客户端连接。

socket函数

​ 为了执行网络I/O,一个进程必须做的第一件事情就是调用socket函数,指定期望的通信协议类型。

#include <sys/socket.h>

int socket(int family, int type, int protocol); // succeed on returning non-negative descriptor
解释
AF_INET IPv4协议
AF_INET6 IPv6协议
AF_LOCAL Unix域协议
AF_ROUTE 路由套接口
AF_KEY 密钥套接口

​ 表1 socket函数的协议族(family)常值

类型 解释
SOCK_STREAM 字节流套接口
SOCK_DGRAM 数据包套接口
SOCK_RAW 原始套接口

​ 表2 socket函数套接口类型(type)

AF_INET AF_INET6 AF_LOCAL AF_ROUTE AF_KEY
SOCKET_STREAM TCP TCP Yes
SOCK_DGRAM UDP UDP Yes
SOCK_RAW IPv4 IPv6 Yes Yes

​ 表3 socket函数的族与类型的组合

​ 并非所有套接口family与type的组合都是有效的,表3给出了一些有效的组合和对应的真正协议。其中标"Yes"的项也是有效的,但还没有找到便捷的缩略词;而空白项则是不支持的。

​ socket函数在成功时返回一个小的非负整数,它与文件描述符类似,我们把它称为套接字接口描述字(socket descriptor),简称套接字(sockfd)。为了得到这个套接口描述字,我们只是制定了协议族(IPv4,IPv6或Unix)和套接口类型(字节流、数据包或原始套接口)。我们并没有指定本地协议地址或远程协议地址。

connect函数

​ TCP客户端用connect函数来建立一个与TCP服务器的连接

#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr* servaddr, socklen_t addrlen); // succeed on returning 0 else failure on returning -1

bind函数

​ 函数bind给套接口分配一个本地协议端口,对于网际协议,协议地址是32位IPv4地址或128位IPv6地址与16位的TCP或UDP端口号的组合。

#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr* myaddr, socklen_t addrlen);

​ 第二个参数是一个指向特定协议的地址结构的指针,第三个参数是该地址结构的长度。对于TCP,调用函数bind可以指定一个端口号,指定一个IP地址,可以两者都指定,也可以一个都不指定。

listen函数

函数listen仅被TCP服务器调用,它做两件事情:

1.当函数socket创建一个套接口时,它被假设为一个主动套接口,也就是说,它是一个将调用connect发起连接的客户套接口,函数listen将未连接的套接口转换成被动套接口,指示内核应接受指向此套接口的连接。根据TCP状态转换图,调用函数listen导致套接口从CLOSED状态转换到LISTEN状态。

2.函数的第二个参数规定了内核为此套接口排队的最大连接个数。

#include <sys/socket.h>

int listen(int sockfd, int backlog);

accept函数

​ accept由TCP服务器调用,从已完成连接队列头返回下一个已完成连接。若已完成连接队列为空,则进程睡眠。

#include <sys/socket.h>

int accept(int sockfd, struct sockaddr* cliaddr, socklen_t* addrlen);

​ 参数cliaddr和addrlen用来返回连接对方进程的协议地址。addrlen是值-结果参数:调用前,我们将*addrlen所指的整数值置为由cliaddr所指的套接口地址结构的长度,返回时,此整数值即为由内核存在此套接口地址结构内的准确字节数。

​ 如果函数accept执行成功,则返回值时内核自动生成的一个全新描述字,代表与客户的TCP连接。当我们讨论函数accept时,常把它的第一个参数称为监听套接口(listening socket)套接字(由函数socket生成的描述字,用作函数bind和listen的第一个参数),把它的返回值称为已连接套接口(connected socket)描述字。将这两个套接口区分开是很重要的。一个给定的服务器常常是只生成一个监听套接口且一直存在,直到该服务器关闭。内核为每个被接受的客户端连接创建一个已连接口(也就是说内核已为它完成TCP的三路握手过程)。当服务器完成某客户的服务时,关闭已连接套接口。

fork和exec函数

fork是unix中派生新进程的唯一方法

#include <unistd.h>

pid_t fork(void); //返回:子进程中为0,在父进程中为子进程id,-1——出错

​ fork在子进程返回0而不是父进程ID,一个原因是:子进程只有一个父进程,它总可以调用getppid来得到;而父进程有许多子进程,它没有办法来得到各子进程的ID。如果父进程想跟踪所有子进程的ID,它必须记住fork的返回值。我觉得还有一个就是fork在子进程中返回0使得子进程父进程的判断更加简单。

​ fork有两个典型应用:

​ 1.一个进程可以为自己创建一个拷贝,这样,当一个拷贝处理一个操作时,其他的拷贝可以执行其他的任务。这是非常典型的网络服务器。

​ 2.一个进程想执行其他的程序,由于创建新进程的唯一方法是调用fork,进程首先调用fork来生成一个拷贝,然后其中一个拷贝(通常为子进程)调用exec来代替自己去执行新程序。

并发服务器

pid_t pid;
int listenfd, connfd;
listenfd = Socket(...);
// fill in sockaddr_in() with server's well-known port
Bind(listenfd, ...);
Listen(listenfd, LISTENQ);
for(;;) {
   
	// probably blocks
	connfd = Accept(listenfd, ...);
	if((pid==Fork())==0) {
   
		Close(listenfd); // child closes listening socket which is set its reference value -1
		doit(connfd);
		Close(connfd);
		exit(0);
	}
	Close(connfd);
}

每个文件或套接口都有一个访问计数,该访问计数在文件表项中维护,它表示当前指向该文件或套接口的打开的描述字个数。

Close函数

​ 一般Unix函数close也用来关闭套接口,终止TCP连接。

#include <unistd.h>

int close(int sockfd); //返回:0——ok,-1——出错

​ TCP套接口的close其缺省功能是将套接口做上“已关闭”标记,并立即返回到进程。这个套接口描述字不能再为进程所用:它不能用作函数read或write的参数,但TCP将试着发送已排队待发的任何数据,然后按正常的TCP连接终止序列进行操作。

getsockname和getpeername函数

​ 这两个函数或返回与套接口关联的本地协议地址(getsockname),或返回与套接口关联的远程协议地址(getpeername)。

#include <sys/socket.h>

int getsockname(int sockfd, struct sockaddr* localaddr, socklen_t* addrlen);
int getpeername(int sockfd, struct sockaddr* peeraddr, socklen_t* addrlen);

一个使用TCP进行通讯的例子

例子来自[2]https://github.com/troydhanson/network

客户端

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

char *server = "127.0.0.1";  /* loopback */
int port = 6180;             /* no significance */

int main(int argc, char *argv[]) {
   

  char *buf = "hello, world!";
  int buflen = strlen(buf), rc;

  /**********************************************************
   * create an IPv4/TCP socket, not yet bound to any address
   *********************************************************/
  int fd = socket(AF_INET, SOCK_STREAM, 0);
  if (fd == -1) {
   
    printf("socket: %s\n", strerror(errno));
    exit(-1);
  }

  /**********************************************************
   * internet socket address structure, for the remote side
   *********************************************************/
  struct sockaddr_in sin;
  sin.sin_family = AF_INET;
  sin.sin_addr.s_addr = inet_addr(server);
  sin.sin_port = htons(port);

  if (sin.sin_addr.s_addr == INADDR_NONE) {
   
    printf("invalid remote IP %s\n", server);
    exit(-1);
  }

  /**********************************************************
   * Perform the 3 way handshake, (c)syn, (s)ack/syn, c(ack)
   *********************************************************/
  if (connect(fd, (struct sockaddr*)&sin, sizeof(sin)) == -1) {
   
    printf("connec
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值