TCP socket通信
Linux Socket:TCP 客户端与服务器通信
1. Socket 是什么
Socket 是 Linux 提供的网络通信接口。
本例使用 TCP:
socket(AF_INET, SOCK_STREAM, 0);
AF_INET → IPv4
SOCK_STREAM → TCP通信本质:
最终模型
TCP
客户端 服务器
│ │
socket() socket()
│ │
│ bind()
│ │
│ listen()
│ │
connect() ─────────────────→ accept()
│ │
│ │
send() ──────────────────────→ read()
│ │
read() ←────────────────────── send()
│ │
close() close()2. 服务器为什么先 socket()
sfp = socket(AF_INET, SOCK_STREAM, 0);创建一个 Socket,返回文件描述符:
sfp
↓
服务器的 SocketLinux 中 Socket 本质上也是文件描述符,所以后面可以通过:
read()
write()
send()
recv()
close()操作。
3. 服务器为什么要 bind()
创建 Socket 后,它还不知道:
监听哪个 IP?
监听哪个端口?所以先配置地址:
s_add.sin_family = AF_INET;
s_add.sin_addr.s_addr = htonl(INADDR_ANY);
s_add.sin_port = htons(8888);含义:
AF_INET → IPv4
INADDR_ANY → 监听本机所有 IPv4 地址
8888 → 监听端口
htons() → 主机字节序 → 网络字节序然后:
bind(sfp,
(struct sockaddr *)&s_add,
sizeof(s_add));把:
Socket
↓
IP + 8888绑定起来。
4. 为什么还要 listen()
listen(sfp, 5);bind() 只是完成:
“我要使用 8888 端口。”
listen() 才让 Socket 进入:
“等待客户端连接”状态。
所以服务器前半段是:
socket()
↓
创建 Socket
bind()
↓
绑定 IP + 端口
listen()
↓
开始监听5. 客户端怎么知道服务器 IP
客户端:
int main(int argc, char **argv)运行:
./client 192.168.1.100那么:
argc = 2
argv[0] → "./client"
argv[1] → "192.168.1.100"所以:
s_add.sin_addr.s_addr = inet_addr(argv[1]);就是把:
"192.168.1.100"转换成 Socket 使用的 IPv4 地址。
端口:
s_add.sin_port = htons(8888);最终客户端得到:
服务器地址 = 192.168.1.100:88886. 客户端为什么调用 connect()
客户端:
cfd = socket(AF_INET, SOCK_STREAM, 0);创建自己的 Socket。
然后:
connect(cfd,
(struct sockaddr *)&s_add,
sizeof(s_add));意思:
主动连接服务器的
192.168.1.100:8888。
于是:
客户端 cfd
│
│ connect()
↓
服务器监听的 8888TCP 连接建立。
7. listen() 和 accept() 为什么不是一回事
xxxxxxxxxx9 1void myHandler(int sig)2{3 if (sig == SIGINT) {4 printf("I have got SIGINT\n");5 }6 else if (sig == SIGQUIT) {7 printf("I have got SIGQUIT\n");8 }9}c
nfp = accept(sfp,
(struct sockaddr *)&c_add,
&sin_size);这里最重要。
sfp
监听 Socket作用:
只负责等待新客户端。
nfp
连接 Socket作用:
负责和这个客户端真正通信。
所以:
sfp
↓
listen()
↓
等待连接
↓
accept()
↓
nfp
↓
和客户端通信这就是为什么服务器有两个 fd。
8. 建立连接后,开始收发数据
服务器:
send(nfp,
snd_buffer,
strlen(snd_buffer),
0);发送:
Hello!This is Server!客户端:
read(cfd, buffer, 1024);接收。
数据流:
服务器
│
send()
↓
TCP
↓
客户端
│
read()9. 客户端不断发送
客户端:
while(1) {
send(cfd,
snd_buffer,
strlen(snd_buffer),
0);
sleep(1);
}每秒发送一次:
This is Client!服务器:
while(1) {
read(nfp, rcv_buffer, 1024);
printf("%s\n", rcv_buffer);
}不断接收。
最终:
客户端 服务器
send() ─────────────────────→ read()
send() ─────────────────────→ read()
send() ─────────────────────→ read()10. TCP 最重要的特点:字节流
TCP 不保留:
一次
send()对应一次read()的消息边界。
例如发送:
send(cfd, "hello", 5, 0);
send(cfd, "world", 5, 0);接收端不保证得到:
hello
world也可能:
helloworld甚至:
hel
loworld所以实际工程必须自己设计应用层协议,例如:
固定长度
长度字段
分隔符
TLV这是 TCP 编程必须掌握的点。
11. 最终流程
服务器
socket()
↓
bind()
↓
listen()
↓
accept()
↓
send()/read()
↓
close()客户端
socket()
↓
connect()
↓
send()/read()
↓
close()12. 一句话记忆
服务器负责“绑定并等待连接”,客户端负责“主动连接”,连接建立后双方通过 Socket 文件描述符收发数据。
服务器:
sfp → 监听
nfp → 通信
客户端:
cfd → 通信13. 关键代码
服务器建立连接
int sfp = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in s_add;
bzero(&s_add, sizeof(s_add));
s_add.sin_family = AF_INET;
s_add.sin_addr.s_addr = htonl(INADDR_ANY);
s_add.sin_port = htons(8888);
bind(sfp,
(struct sockaddr *)&s_add,
sizeof(s_add));
listen(sfp, 5);
int nfp = accept(sfp,
(struct sockaddr *)&c_add,
&sin_size);客户端建立连接
int cfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in s_add;
bzero(&s_add, sizeof(s_add));
s_add.sin_family = AF_INET;
s_add.sin_addr.s_addr = inet_addr(argv[1]);
s_add.sin_port = htons(8888);
connect(cfd,
(struct sockaddr *)&s_add,
sizeof(s_add));数据收发
// 服务器发送
send(nfp, snd_buffer, strlen(snd_buffer), 0);
// 客户端接收
read(cfd, buffer, 1024);
// 客户端发送
send(cfd, snd_buffer, strlen(snd_buffer), 0);
// 服务器接收
read(nfp, rcv_buffer, 1024);完整代码
server
#include <stdlib.h>
#include <sys/types.h>
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
int main(){
int sfp, nfp, num = 0;
struct sockaddr_in s_add,c_add;
int sin_size;
unsigned short portnum=8888;
char snd_buffer[100] = "Hello!This is Server!";
char rcv_buffer[1000] = {0};
printf("Hello,welcome to my server !\r\n");
sfp = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == sfp){
printf("socket fail ! \r\n");
return -1;
}
printf("socket ok !\r\n");
bzero(&s_add,sizeof(struct sockaddr_in));
s_add.sin_family=AF_INET;
s_add.sin_addr.s_addr=htonl(INADDR_ANY); //INADDR_ANY的值为0
s_add.sin_port=htons(portnum);
if(-1 == bind(sfp,(struct sockaddr *)(&s_add), sizeof(struct sockaddr))){
printf("bind fail !\r\n");
return -1;
}
printf("bind ok !\r\n");
if(-1 == listen(sfp,5)){
printf("listen fail !\r\n");
return -1;
}
printf("listen ok\r\n");
sin_size = sizeof(struct sockaddr_in);
nfp = accept(sfp, (struct sockaddr *)(&c_add), &sin_size);
if(-1 == nfp){
printf("accept fail !\r\n");
return -1;
}
printf("accept ok!\r\nServer start get connect from %#x : %#x\r\n", ntohl(c_add.sin_addr.s_addr), ntohs(c_add.sin_port));
send(nfp, snd_buffer, strlen(snd_buffer), 0); //接受连接成功后, 向客户端发问候信息
while(1){
read(nfp, rcv_buffer,1024);
printf("%s\r\n",rcv_buffer);
}
close(nfp);
close(sfp);
return 0;
}client
#include <stdlib.h>
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char **argv){
int cfd;
int recbyte;
int sin_size;
char snd_buffer[100] = "This is Client!";
char buffer[1024] = {0};
struct sockaddr_in s_add, c_add;
unsigned short portnum = 8888;
printf("Hello,welcome to client!\r\n");
if(argc != 2){
printf("usage: echo ip\n");
return -1;
}
cfd = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == cfd){
printf("socket fail ! \r\n");
return -1;
}
printf("socket ok !\r\n");
bzero(&s_add,sizeof(struct sockaddr_in));
s_add.sin_family=AF_INET;
s_add.sin_addr.s_addr= inet_addr(argv[1]);
s_add.sin_port=htons(portnum);
printf("s_addr = %#x, port: %hu\r\n", s_add.sin_addr.s_addr, portnum);
if(connect(cfd,(struct sockaddr *)(&s_add), sizeof(struct sockaddr)) == -1){
printf("connect fail !\r\n");
return -1;
}
printf("connect ok !\r\n");
read(cfd, buffer, 1024);
printf("%s\r\n",buffer);
while(1){
send(cfd, snd_buffer, strlen(snd_buffer), 0);
sleep(1);
}
close(cfd);
return 0;
}
