TCP(Transmission Control Protocol),由RFC 793定义,中文名为传输控制协议,是一种面向连接的、可靠的、基于字节流的传输层通信协议。
TCP是常用的网络传输协议之一,该协议是面向连接、可靠传输的字节流协议。在Linux C网络程序中广泛使用,如http/https、ftp等。
TCP通信分为客户端和服务端,传输数据前需要客户端向服务端发起建立连接,数据传输完成后,双方可以断开连接。
#include
#include
int socket(int domain, int type, int protocol);
作用: 用来创建一个通信的终端实例
参数说明:
domain: 协议族,用AF_INET表示IPv4
type: 传输方式,常用的有以下两种
SOCK_STREAM: TCP
SOCK_DGRAM: UDP
protol: 特殊协议,实际应用中都是写为0
返回值:
成功时返回一个socket文件描述符,失败时返回-1,errno会被设置,可以通过errno值获取错误码
#include
#include
int bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
作用:绑定网络地址(IP/PORT)到socket
参数说明:
sockfd: socket()返回的描述符
addr: 绑定的地址
addrlen: 绑定的地址结构长度
返回值:
成功时返回0,失败时返回-1,errno会被设置,可以通过errno值获取错误码
#include
#include
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
作用:连接到服务端
参数说明:
sockfd: socket()返回的描述符
addr: 服务端的地址
addrlen: 服务端的地址结构长度
返回值:
成功时返回0,失败时返回-1,errno会被设置,可以通过errno值获取错误码
#include
#include
int listen(int sockfd, int backlog);
作用:监听客户端的连接请求
参数说明:
sockfd: socket()返回的描述符
backlog: 待处理的客户端连接请求的队列最大长度,如果待处理的队列满了,则收到客户端请求会返回
返回值:
成功时返回0,失败时返回-1,errno会被设置,可以通过errno值获取错误码
#include
#include
int accept(int sockfd, struct sockaddr *addr,
socklen_t *addrlen);
作用:接受客户端的连接请求
参数说明:
sockfd: socket()返回的描述符
addr: 服务端的地址
addrlen: 服务端的地址结构长度
返回值:
成功时返回对应客户端连接的socket描述符,失败时返回-1,errno会被设置,可以通过errno值获取错误码
#include
ssize_t write(int fd, const void *buf, size_t count);
#include
ssize_t read(int fd, void *buf, size_t count);
作用:
可以通过系统的write/read函数发送/接收网络数据,其中fd对应socket文件描述符
参数说明:
fd: socket()返回的网络描述符
buf:接收或发送缓冲区
count:发送的字节数或接收的缓冲区大小
返回值:
大于0时,表示读取或写入的字节数; -1表示错误
#include
int close(int fd);
作用:
关闭一个描述符(文件/socket)
server_tcp.c:
#include
#include
#include
#include
#include
#include
#include
#define SERVER_PORT 9999
int server_tcp()
{
int ret = 0;
int socket_fd = -1;
int client_fd = -1;
int addr_len = 0;
unsigned int value = 1;
char buf[1024] = {0};
int read_len = 0;
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if (socket_fd < 0) {
printf("%s: socket failed\n", __FUNCTION__);
goto on_error;
}
if (setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR,
(void *)&value, sizeof(value)) < 0) {
printf("%s: Fail to setsockopt\n", __FUNCTION__);
goto on_error;
}
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr_len = sizeof(server_addr);
ret = bind(socket_fd, (const struct sockaddr *)&server_addr, addr_len);
if (ret < 0) {
printf("%s: bind failed\n", __FUNCTION__);
goto on_error;
}
ret = listen(socket_fd, 5);
if (ret < 0) {
printf("%s: listen failed\n", __FUNCTION__);
goto on_error;
}
client_fd = accept(socket_fd
, (struct sockaddr *)&client_addr, &addr_len);
if (client_fd < 0) {
printf("%s: invalid client fd[%d]\n", __FUNCTION__, client_fd);
return -1;
}
printf("client fd[%d] connect\n", client_fd);
while (1) {
read_len = read(client_fd, buf, 1024);
if (read_len > 0) {
printf("recv data[%s] from client\n", buf);
write(client_fd, buf, read_len);
printf("send data[%s] to client\n", buf);
} else if (read_len < 0) {
printf("errno:%d\n", errno);
break;
} else if (0 == read_len) {
printf("client fd[%d] disconnect\n", client_fd);
break;
}
}
close(socket_fd);
close(client_fd);
return 0;
on_error:
close(socket_fd);
close(client_fd);
return -1;
}
int main(int argc, char *argv[])
{
int ret = -1;
ret = server_tcp();
if (ret < 0) {
printf("%s: server tcp failed\n", __FUNCTION__);
return -1;
}
return 0;
}
client_tcp.c:
#include
#include
#include
#include
#include
#include
#include
#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 9999
int client_tcp(char *server_ip, int server_port, char *data)
{
int ret = 0;
int socket_fd = -1;
int addr_len = 0;
char buf[1024] = {0};
struct sockaddr_in server_addr;
socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if (socket_fd < 0) {
printf("%s: socket failed\n", __FUNCTION__);
return 0;
}
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(server_port);
server_addr.sin_addr.s_addr = inet_addr(server_ip);
addr_len = sizeof(server_addr);
ret = connect(socket_fd, (struct sockaddr *)&server_addr, addr_len);
if (ret < 0) {
printf("%s: connect failed, errno:%d, %s\n", __FUNCTION__, errno, strerror(errno));
goto on_error;
}
ret = write(socket_fd, data, strlen(data));
printf("send data[%s] to server\n", data);
ret = read(socket_fd, buf, sizeof(buf));
if (ret > 0) {
printf("recv data[%s] from server\n", buf);
} else if (ret < 0) {
printf("%s: errno:%d\n", __FUNCTION__, errno);
} else if (0 == ret) {
printf("server fd[%d] disconnect\n", socket_fd);
}
close(socket_fd);
return 0;
on_error:
close(socket_fd);
return -1;
}
int main(int argc, char *argv[])
{
if (argc < 4) {
printf("usage: %s 127.0.0.1 9999 hello\n", argv[0]);
return -1;
}
client_tcp(argv[1], atoi(argv[2]), argv[3]);
return 0;
}
Makefile:
all:
gcc -o server server_tcp.c
gcc -o client client_tcp.c
clean:
-@rm server client
编译:
make
运行:
$ ./server
client fd[4] connect
recv data[helloworld] from client
send data[helloworld] to client
client fd[4] disconnect
$ ./client 127.0.0.1 9999 helloworld
send data[helloworld] to server
recv data[helloworld] from server
服务端不启动时,客户端连接失败,如下:
$ ./client 127.0.0.1 9999 helloworld
client_tcp: connect failed, errno:111, Connection refused
服务端启动时,可以看见监听的端口,如下:
$ netstat -antp | grep server
tcp 0 0 0.0.0.0:9999 0.0.0.0:* LISTEN 36467/server
已经有服务端启动时,再启动服务端bind()接口会失败:
$ ./server
bind failed, errno:98, Address already in use
为了使服务端能够多次处理连接,使用epoll函数管理socket描述符。
源码:
server_tcp.c:
#include
#include
#include
#include
#include
#include
#include
#include
#define SERVER_PORT 9999
int g_listen_fd = -1;
int g_epoll_fd = -1;
int server_tcp()
{
int ret = 0;
long socket_fd = -1;
int addr_len = 0;
unsigned int value = 1;
struct epoll_event event;
struct sockaddr_in server_addr;
socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if (socket_fd < 0) {
printf("%s: socket failed\n", __FUNCTION__);
goto on_error;
}
if (setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR,
(void *)&value, sizeof(value)) < 0) {
printf("%s: Fail to setsockopt\n", __FUNCTION__);
goto on_error;
}
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr_len = sizeof(server_addr);
ret = bind(socket_fd, (const struct sockaddr *)&server_addr, addr_len);
if (ret < 0) {
printf("%s: bind failed\n", __FUNCTION__);
goto on_error;
}
ret = listen(socket_fd, 5);
if (ret < 0) {
printf("%s: listen failed\n", __FUNCTION__);
goto on_error;
}
event.events = EPOLLIN;
event.data.fd = socket_fd;
ret = epoll_ctl(g_epoll_fd, EPOLL_CTL_ADD, socket_fd, &event);
if(ret < 0) {
printf("epoll_ctl failure:%s\n", strerror(errno));
close(g_epoll_fd);
return -1;
}
g_listen_fd = socket_fd;
return 0;
on_error:
close(socket_fd);
return -1;
}
int main(int argc, char *argv[])
{
int ret = -1;
int num = 0;
int i = 0;
int client_fd = 0;
int addr_len = 0;
int read_len = 0;
char buf[1024] = {0};
struct epoll_event event;
struct sockaddr_in client_addr;
struct epoll_event events_array[10];
g_epoll_fd = epoll_create(10);
if (g_epoll_fd < 0) {
printf("epoll_create failure:%s\n", strerror(errno));
return -1;
}
ret = server_tcp();
if (ret < 0) {
printf("%s: server tcp failed\n", __FUNCTION__);
return -1;
}
while (1) {
num = epoll_wait(g_epoll_fd, events_array, 10, -1);
if (num < 0) {
printf("epoll_wait failure:%s\n", strerror(errno));
close(g_epoll_fd);
break;
} else if (num == 0) {
printf("eopll_wait timeout!\n");
continue;
}
for (i = 0; i < num; i++) {
if(events_array[i].events == EPOLLIN) {
if (events_array[i].data.fd == g_listen_fd) {
client_fd = accept(events_array[i].data.fd,
(struct sockaddr *)&client_addr, &addr_len);
if (client_fd < 0) {
printf("%s: invalid client fd[%d]\n", __FUNCTION__, client_fd);
return -1;
}
printf("client %s:%d fd[%d] connect\n",
inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port), client_fd);
event.events = EPOLLIN;
event.data.fd = client_fd;
ret = epoll_ctl(g_epoll_fd, EPOLL_CTL_ADD, client_fd, &event);
if(ret < 0) {
printf("epoll_ctl failure:%s\n", strerror(errno));
return -1;
}
} else {
memset(buf, 0, sizeof(buf));
read_len = read(events_array[i].data.fd, buf, 1024);
if (read_len > 0) {
printf("recv data[%s] from client fd[%d]\n",
buf, events_array[i].data.fd);
} else if (read_len < 0) {
epoll_ctl(g_epoll_fd, EPOLL_CTL_DEL, events_array[i].data.fd, NULL);
close(events_array[i].data.fd);
} else if (0 == read_len) {
printf("client fd[%d] disconnect\n",
events_array[i].data.fd);
epoll_ctl(g_epoll_fd, EPOLL_CTL_DEL, events_array[i].data.fd, NULL);
close(events_array[i].data.fd);
}
}
}
}
}
return 0;
}
client_tcp.c:
#include
#include
#include
#include
#include
#include
int client_tcp(char *server_ip, int server_port, char *data)
{
int ret = 0;
int socket_fd = -1;
int addr_len = 0;
struct sockaddr_in server_addr;
socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if (socket_fd < 0) {
printf("%s: socket failed\n", __FUNCTION__);
return 0;
}
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(server_port);
server_addr.sin_addr.s_addr = inet_addr(server_ip);
addr_len = sizeof(server_addr);
ret = connect(socket_fd, (struct sockaddr *)&server_addr, addr_len);
if (ret < 0) {
printf("%s: connect failed\n", __FUNCTION__);
goto on_error;
}
ret = write(socket_fd, data, strlen(data));
close(socket_fd);
return 0;
on_error:
close(socket_fd);
return -1;
}
int main(int argc, char *argv[])
{
if (argc < 4) {
printf("usage: %s 127.0.0.1 9999 hello\n", argv[0]);
return -1;
}
client_tcp(argv[1], atoi(argv[2]), argv[3]);
return 0;
}
Makefile:
all:
gcc -o server server_tcp.c
gcc -o client client_tcp.c
clean:
-@rm server client
客户端端口变化问题
$ ./server
client 127.0.0.1:50280 fd[5] connect
recv data[hello] from client fd[5]
client fd[5] disconnect
client 127.0.0.1:50281 fd[5] connect
recv data[hello] from client fd[5]
client fd[5] disconnect
client 127.0.0.1:50282 fd[5] connect
recv data[hello] from client fd[5]
client fd[5] disconnect
客户端端口变化是由于没有绑定指定的端口,系统每次随机分配了一个端口。
memset(&client_addr, 0, sizeof(client_addr));
client_addr.sin_family = AF_INET;
client_addr.sin_port = htons(10000);
client_addr.sin_addr.s_addr = inet_addr(“127.0.0.1”);
addr_len = sizeof(client_addr);
ret = bind(socket_fd, (const struct sockaddr *)&client_addr, addr_len);
if (ret < 0) {
printf("%s: bind failed\n", __FUNCTION__);
close(socket_fd);
return 0;
}
测试:
多次启动客户端,均为同一个端口10000
$ ./server
client 127.0.0.1:10000 fd[5] connect
recv data[hello] from client fd[5]
client fd[5] disconnect
client 127.0.0.1:10000 fd[5] connect
recv data[hello] from client fd[5]
client fd[5] disconnect
###3.3 绑定失败问题
$ ./client 127.0.0.1 9999 hello
$ ./client 127.0.0.1 9999 hello
client_tcp: bind failed, errno:98, Address already in use
$ netstat -antp | grep 10000
tcp 0 0 127.0.0.1:10000 127.0.0.1:9999 TIME_WAIT
原因是之前的连接还没有完全释放,可以在调用bind()接口前将socket设置为地址可重复使用,对客户端、服务端同样适用。
socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if (socket_fd < 0) {
printf("%s: socket failed\n", __FUNCTION__);
return 0;
}
unsigned int value = 1;
if (setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR,
(void *)&value, sizeof(value)) < 0) {
printf("%s: Fail to setsockopt\n", __FUNCTION__);
goto on_error;
}
memset(&client_addr, 0, sizeof(client_addr));
client_addr.sin_family = AF_INET;
client_addr.sin_port = htons(CLINET_PORT);
client_addr.sin_addr.s_addr = inet_addr(CLINET_IP);
测试:
$ ./client 127.0.0.1 9999 hello
$ ./client 127.0.0.1 9999 hello
$ ./client 127.0.0.1 9999 hello
这时就没有绑定问题了
$ ./server 127.0.0.1 9999
client 127.0.0.1:10000 fd[5] connect
recv data[hello] from client fd[5]
client fd[5] disconnect
在同一台机器,运行client可以访问
$ ./client 127.0.0.1 9999 hello
但是在另外一台机器,是连接不上的,因为127.0.0.1是本地回环地址,只能在同一台机器内使用,如下:
$ ./client 127.0.0.1 9999 hello
client_tcp: connect failed
因此,server端需要绑定其它机器可以访问的地址。比如server 端所在机器是192.168.0.29,可以绑定在192.168.0.29。
测试结果:
$ ./server 192.168.0.29 9999
client 192.168.0.132:10000 fd[5] connect
recv data[hello] from client fd[5]
client fd[5] disconnect
$ ./client 192.168.0.29 9999 hello
同时,同一台机器也可以通过127.0.0.1连接
$ ./server 192.168.0.29 9999
client 127.0.0.1:10000 fd[5] connect
recv data[hello] from client fd[5]
client fd[5] disconnect
$ ./client 192.168.0.29 9999 hello
但是在实际环境中,比如云服务器,主机上一般都有多块网上、多个IP,如本地回环地址127.0.0.1、局域网IP(也称私网地址)、公网IP等,如下某主机IP地址情况:
本地回环地址:127.0.0.1
私有IP:192.168.0.5, 192.168.1.5
公网地址:36.87.80.2,36.78.90.3
如果服务端监听在192.168.0.5地址,客户端192.168.0.x可以连接,但客户端192.168.1.x连接不上;如果服务端监听在192.168.1.5地址,客户端192.168.1.x可以连接,但客户端192.168.0.x连接不上。
有一种方法是同时监听需要监听的地址,常用于公网服务器。另一种方法是直接监听在0.0.0.0地址,即表示监听主机的所有IP地址,这也是非常常用的方法。