网路通信的本质是两个不同的主机在进行数据交互,在交互的过程中,数据是不会自己决定流向,这就需要有标示来指明数据从哪里来要到那里去。
IP地址在网络中标识了唯一的一台主机,只要获取到对方的IP地址,就可以给对方发送数据。
IP地址在IP协议中是用来标示网络中唯一的一台主机
IP协议不同,对应的IP地址也不同,对于IPv4来说,IP地址是一个4字节32位的整数
我们通常使用点分十进制来表示一个IP地址,例如192.168.0.1,每个用小数点分割开来的数字表示一个字节,范围是0-255
一台主机上面可能运行了好多个进程,当主机接收到数据OS就需要决定把这个数据交给哪个进程来进行解析,但是交给哪个进程来解析呢,OS无法自行决定,这就需要一个标识来说明这是给哪个进程的。这就是端口号。
IP地址标识了网络中的唯一一台主机,端口号标识了一台主机上的唯一一个进程
端口号是一个2字节16位的整数
端口号标识了一个进程,告诉OS这个数据要交给哪一个进程来处理
IP+端口号能标识网络上的唯一一台主机好的唯一一个进程
一个端口号只能被一个进程占用
一个进程可以绑定多个端口号,但是一个端口号只能被一个进程绑定
套接字可以简单理解为IP+端口号的组合。
网络上进行通信还需要一个五元组:通信协议+源IP+源端口+目的IP+目的端口
TCP:传输控制协议
传输层协议
有连接
面向字节流
可靠传输
UDP:用户数据报协议
传输层协议
无连接
面向数据报
不可靠传输
在网络应用中,两个通信的进程最常用的就是客户端服务器模式(C/S模式),即客户端向服务器发起请求,服务器接收到请求之后对客户端提供相应的服务。
服务器先启动,通知本地主机,可以接收请求
等待客户端发起请求
接收到客户端请求,处理请求并发送应答信号
返回第二步,等待新的客户端连接
关闭服务器
打开通信通道,连接到服务器所在主机的特定端口
向服务器发起请求,等待接收并应答。
请求结束后关闭通信通道
创建套接字
#include
#include
int socket(int domain, int type, int protocol); //客户端服务器
参数domain指明通信发生的区域,常用AF_INET
type指明建立套接字的类型:
TCP流式套接字(SOCK_STREAM)
UDP数据报式套接字(SOCK_DGRAM)
原始式套接字(SOCK_RAW)
protocol说明该套接字使用的特定协议,一般为0使用默认连接
socket根据这三个参数建立一个套接字并将响应资源分配给它,同时返回一个整形的套接字号
绑定端口号
#include
#include
int bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
当一个套接字创建出来后是没有被命名的,bind将套接字地址与所创建的套接字建立联系。
参数sockfd是创建的套接字号,addr是本地套接字
监听socket
#include
#include
int listen(int sockfd, int backlog); //TCP
sockfd是需要监听的套接字号
backlog是请求连接队列的最大长度
建立连接
#include
#include
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen); //TCP客户端
sockfd是想要建立连接的本地套接字描述符
addr是对方套接字地址结构的指针
接收请求
#include
#include
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);//TCP服务器
sockfd是本地套接字描述符
在调用accpet之前应先调用listen
addr指向客户端套接字结构的指针
accept()用于面向连接服务器。参数addr和addrlen存放客户方的地址信息。调用前,参数addr 指向一个初始值为空的地址结构,而addrlen 的初始值为0;调用accept()后,服务器等待从编号为sockfd的套接字上接受客户连接请求,而连接请求是由客户方的connect()调用发出的。当有连接请求到达时,accept()调用将请求连接队列上的第一个客户方套接字地址及长度放入addr 和addrlen,并创建一个与sockfd有相同特性的新套接字号。新的套接字可用于处理服务器并发请求。
//TCP-server
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
if(argc != 3){
printf("Usage : %s [ip] [port]\n", argv[0]);
return -1;
}
int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
if(listen_sock < 0){
perror("socket");
return 1;
}
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(atoi(argv[2]));
local.sin_addr.s_addr = inet_addr(argv[1]);
socklen_t len = sizeof(local);
if(bind(listen_sock, (struct sockaddr*)&local, len) < 0){
perror("bind");
return 2;
}
if(listen(listen_sock, 5) < 0){
perror("listen");
return 3;
}
char buf[1024] = {0};
while(1){
struct sockaddr_in client;
len = sizeof(client);
int sock = accept(listen_sock, (struct sockaddr*)&client, &len);
if(sock < 0){
perror("accept");
continue;
}
printf("get a new link : [%s] [%d]\n", \
inet_ntoa(client.sin_addr), ntohs(client.sin_port));
//recv data
ssize_t s = recv(sock, buf, sizeof(buf)-1, 0);
if(s < 0){
perror("recv");
close(sock);
continue;
}else if(s == 0){ //连接中断
printf("perr shutdown\n");
close(sock);
continue;
}
buf[s] = 0;
printf("[ip : port] : %s", inet_ntoa(client.sin_addr), \
ntohs(client.sin_port), buf);
//send data
memset(buf, 0x00, sizeof(buf));
scanf("%s", buf);
send(sock, buf, strlen(buf), 0);
}
}
客户端程序:
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char *argv)
{
if(argc != 3){
printf("Usage : %s [ip] [port]\n", argv[0]);
return -1;
}
int sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock < 0){
perror("socket");
return -2;
}
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(atoi(argv[2]));
local.sin_addr.s_addr = inet_addr(argv[1]);
socklen_t len = sizeof(local);
if(connect(sock, (struct sockaddr*)&local, len) < 0){
perror("connect");
return -3;
}
char buf[1024] = {0};
while(1){
printf("Please Enter#:")
scanf("%s", buf);
send(sock, buf, strlen(buf), 0);
memset(buf, 0x00, sizeof(buf));
ssize_t s = recv(sock, buf, sizeof(buf)-1, 0);
if(s < 0){
perror("recv");
close(sock);
continue;
}
else if(s == 0){
printf("perr shutdown!\n");
close(sock);
continue;
}
buf[s] = 0;
printf("server : %s\n", buf);
}
}