TCP(Transmission Control Protocol):传输控制协议。
1)面向连接的运输层协议,点对点连接;
2)提供可靠交付;
3)提供全双工通信;
4)面向字节流。
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
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 \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
#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 \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;
}