网络协议(二)Socket编程之TCP

TCP(Transmission Control Protocol):传输控制协议。

1)面向连接的运输层协议,点对点连接;
2)提供可靠交付;
3)提供全双工通信;
4)面向字节流。

TCP连接的建立和终止
1、TCP连接的建立
1)服务器必须准备好接受外来的连接。由socket、bind和listen来完成被动打开(passive open)。
2)客户通过调用connect发起主动打开(active open)。这导致客户TCP发送一个SYN分节,它告诉服务器客户将在(待建立的)连接中发送的数据的初始序列号。
3)服务器必须确认(ACK)客户的SYN,同时也发送一个SYN分节,它含有服务器将在同一连接中发送的数据的初始化序列。服务器在单个分节中发送SYN和对客户SYN的ACK。
4)客户必须确认(ACK)服务器的SYN。
这种交换至少需要3个分组,因此称之为TCP的三路握手(three-way handshake)。
 
2、TCP连接的终止
1)某个应用程序首先调用close,执行主动关闭(active close)。该端的TCP发送一个FIN分节,表示数据发送完毕。
2)接受到这个FIN的对端执行被动关闭(passive close)。它的接受也作为一个文件结束符(end-of-file)传递给接受端应用进程。
3)一段时间后,接受到这个文件结束符的应用程序将调用close关闭它的套接字。这导致它的TCP也发送一个FIN。
4)接受这个最终FIN的原发送端TCP确认这个FIN。
由于每个方向都需要一个FIN和一个ACK,因此通常需要4个分节,但有些分节可以合并。
无论是客户端还是服务器端,都可以执行主动关闭。
 
3、TCP选项
每个SYN可以包含多个TCP选项。常用的选项有:
1)MSS选项
发送SYN的TCP端使用本选项告诉对端它的最大分分节大小(maximum segment size),
也就是它在本连接的每个TCP分节中愿意接受的最大数据量。
2)窗口规模选项
窗口指的是发送本报文段的一方的接受窗口。
窗口字段明确指出了现在允许对方发送的数据量。窗口值是经常在动态变化着。
3)时间戳选项

4、TCP状态转换图(state transition diagram)

5、TCP Socket编码流程



A socket is an abstraction of a communication endpoint. Just as they would use file descriptors to access files, applications use socket descriptors to access sockets.  (从内核的角度来看,一个套接字就是通信的一个端点;从应用程序的角度看,套接字就是一个有相应描述符的打开文件。)

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

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
1)TCP客户端用connect函数来建立与TCP服务器的连接。
UDP也可以使用connect函数建立已连接UDP套接字(connected UDP socket),与未连接UDP套接字(unconnected UDP socket)对应。
2)客户在调用connect函数不必非要调用bind函数。
因为如果需要的话,内核会确定源IP地址,并选择一个临时端口作为源端口。
3)如果是TCP套接字,调用connect函数将激发TCP的三路握手过程。
按照TCP状态转换图,connect函数导致当前套接字从CLOSED状态转移到SYN_SEND状态,若成功则再转移到ESTABLISHED状态;
若失败则该套接字不再可用,必须关闭,不能对该套接字再次调用connect函数。
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

bind函数将一个本地协议地址与一个套接字关联起来。
调用bind函数可以指定IP地址或端口,也可以不指定。
int listen(int sockfd, int backlog);
1)当socket函数创建一个套接字时,它被假设为一个主动套接字,也就是说,它是一个将调用connect发起连接的客户套接字。listen函数把一个未连接的套接字转换成一个被动套接字,指示内核应接受指向该套接字的连接请求。
2) The backlog argument defines the maximum length to  which  the  queue  of pending connections for sockfd may grow.  
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
1)addr,addrlen用来返回已连接的对端进程(客户)的协议地址。

typedef uint16_t sa_family_t;
typedef uint16_t in_port_t;

/* Generic socket address structure (for connect, bind, and accept) */
struct sockaddr {
	sa_family_t sa_family;   /* Protocol family */
	char        sa_data[14]; /* Address data */
};

typedef uint32_t in_addr_t;

// AF_INET
struct in_addr {      /* IPv4 4-byte address */
	in_addr_t s_addr; /* Unsigned 32-bit integer */
};

/* Internet-style socket address structure */
struct sockaddr_in {
	sa_family_t    sin_family;  /* Address family (always AF_INET)*/
	in_port_t      sin_port;    /* Port number in network byte order */
	struct in_addr sin_addr;    /* IP address in network byte order */
	unsigned char  sin_zero[8]; /* Pad to sizeof(struct sockaddr) */
};

// AF_INET6
struct int6_addr {
	uint8_t s6_addr[16];  /* IPv6 address */
};

struct sockaddr_in6 {
	sa_family_t     sin6_family;   /* address family */
	in_port_t       sin6_port;     /* port number */
	uint32_t        sin6_flowinfo; /* traffic class and flow info */
	struct in6_addr sin6_addr;     /* IPv6 address */
	uint32_t        sin6_scope_id; /* set of interfaces for scope */
};

// AF_LOCAL
struct sockaddr_un {
	sa_family_t sun_family;     
	char        sum_path[104]; /* null-terminated pathname */
}
/* client.c */
#include "socket.h"

int open_clientfd(char *hastname, int port)
{
	int clientfd;
	struct hostent;
	struct sockaddr_in serveraddr;
	
	/* Create a socket descriptor */
	if ((listenfd = socket(AF_INET, SOCK_STREAM, )) < 0) {
		return -1; /* Check errno for cause of error */
	}
	
	/* Fill in the server's IP address and port */
	if ((hp = gethostbyname(hostname)) == NULL) {
		return -2; /* Check h_errno for cause of error */
	}
	memset(&serveraddr, 0, sizeof(serveraddr));
	serveraddr.sin_family = AF_INET;
	memcpy((char *)&serveraddr.sin_addr.s_addr, (char *)hp->h_addr_list[0],
		hp->h_length);
	serveraddr.sin_port = htons((unsigned short)port);
	
	/* Establish a connection with the server */
	if (connect(clientfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0) {
		return -1;
	}
	
	return clientfd;
}

int main(int argc, char **argv)
{
	int clientfd;
	int port;
	char *host;
	char buf[MAXLINE];
	rio_t rio;
	
	if (argc != 3) {
		fprintf(stderr, "usage: %s <host> <port>\n", argv[0]);
		return -1;
	}
	host = argv[1];
	port = atoi(argv[2]);
	
	clientfd = open_clientfd(host, port);
	rio_readinitb(&rio, clientfd);
	
	while (fgets(buf, MAXLINE, stdin) != NULL) {
		rio_writen(clientfd, buf, strlen(buf));
		rio_readlineb(&rio, buf, MAXLINE);
		fputs(buf, stdout);
	}
	close(clientfd);
	
	return 0;
}
/* server.c */
#include <stdio.h>
#include "socket.h"

#define LISTENQ 1024
#define MAXLINE 1024

int open_listenfd(int port)
{
	int listenfd;
	int optval = 1;
	struct socketaddr_in serveraddr;
	
	/* Create a socket descriptor */
	if ((listenfd = socket(AF_INET, SOCK_STREAM, )) < 0) {
		return -1;
	}
	
	/* Eliminates "Address already in uss" error from bind */
	if (setsocketopt(listenfd, SOL_SOCKET, SO_REUSERADDR,
		(const void *)&optval, sizeof(int)) < 0) {
		return -1;
	}
	
	/* Bind(Associate) an address with a socket descriptor */
	/* listenfd will be an end point for all requsets to port on any IP address for this host */
	memset(&serveraddr, 0, sizeof(serveraddr));
	serveraddr.sin_family      = AF_INET;
	serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
	serveraddr.sin_port        = htons((unsigned short)port);
	if (bind(listenfd, (struct socketaddr *)&serveraddr, sizeof(serveraddr)) < 0) {
		return -1;
	}
	
	/* Make it a listening socket ready to accept connection requsets*/
	if (listen(listedfd, LISTENQ) < 0) {
		return -1;
	}
	
	return listedfd;
}

void echo(int connfd)
{
	size_t n;
	char buf[MAXLINE];
	rio_t rio;
	
	rio_readinitb(&rio, connfd);
	while ((n = rio_readlineb(&rio, buf, MAXLINE)) != 0) {
		printf("server received %d bytes\n", n);
		rio_writen(connfd, buf, n);
	}
}

int main(int argc, char **argv)
{
	int listenfd;
	int connfd;
	int port;
	int clientlen;
	struct sockaddr_in clientaddr;
	struct hostent *hp;
	char *haddrp;
	
	if (argc != 2) {
		fprintf(stderr, "usage: %s <port>\n", argv[0]);
		return -1;
	}
	port = atoi(argv[1]);
	
	listedfd = open_listenfd(port);
	while (1) {
		clientlen = sizeof(clientaddr);
		if (connfd = accept(listedfd, (struct sockaddr *)&clientaddr, &clientlen) < 0) {
			return -1;
		}
		
		/* Determine the domain name an IP address of the client */
		hp = gethostbyaddr((const char *)&clientaddr.sin_addr.s_addr,
			sizeof(clientaddr.sin_addr.s_addr), AF_INET);
		haddrp = inet_ntoa(clientaddr.sin_addr);
		printf("server connected to %s (%s)\n", hp->h_name, haddrp);

		echo(connfd);
		close(connfd);
	}
	
	return 0;
}





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值