Linux之socket编程(上)
目录
理解IP和端口号
socket编程接口
简单UDP网络小程序实现
本期我们将开始学习Linux计算机网络的相关知识。
理解IP和端口号
IP:一个IP唯一标识一个网络中的主机。
端口号:唯一标识一个主机中的一个进程。
IP+端口号我们也称作套接字。
所以我们可以得出一个结论,IP+端口号可以唯一标识一个计算机中的一个进程。
在学习Linux操作系统的时候我们也学习过进程的概念,在操作系统中我们使用pid来唯一标识计算机中的一个进程。那么套接字和进程pid冲突吗?
其实也不冲突,就如学生的身份证号和学生在学校的学号一样,都可以唯一标识学生的身份。那为什么在学校不用学生的身份证号作为学生的学号呢?其实这是为了解耦,如果真的使用身份证号作为学生的学号,那么万一有一天身份证号报废了,学生在学校的学号也就报废了。所以为了防止类似的情况出现,我们使用了学号,实现了学校与外界环境的解耦。在计算机网络中使用套接字也是类似的原因。
socket编程接口
// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);
// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address, socklen_t address_len);
// 开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);
// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address, socklen_t* address_len);
// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
在计算机网络通信中,socket通信方式有很多种,网络套接和域间套接,但是不难发现,在socket编程中只有唯一的接口。那么针对不同的socket通信方式怎么样保证只用上述唯一的接口就能实现通信呢?
回答这个问题之前,我们先来了解一下,不同通信方式所对应的数据结构。有三种数据结构,准确来说有两种数据结构。图示如下。
struct sockaddr_in 对应网络套接,struct sockaddr_un对应域间套接。这两个数据结构可以强转成struct sockaddr通用结构,进而使得多种socket通信方式使用同一套socket接口。
简单UDP网络小程序实现
题设:使用socket相关api接口,实现client客户端进程发送任意消息,sever服务器端进程接收到“你好之后”,给client客户端进程返回“hello”功能的程序。
客户端代码如下。
udp_client.cc
#include <iostream>
#include <sys/types.h>
#include <cerrno>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string>
int main(int argc, char *argv[])
{
// 客户端
// 1.创建套接字,打开网络文件
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0)
{
std::cout << "socket create error" << errno << std::endl;
return 1;
}
// 客户端需要绑定ip和端口吗
// 不需要,因为客户端是向服务器端发送数据的,所以不需要,ip和端口号,
// 发送数据时,操作系统自动绑定
//2.使用服务
while (1)
{
// 给谁发
struct sockaddr_in sever;
sever.sin_family = AF_INET;
sever.sin_port = htons(atoi(argv[2]));
sever.sin_addr.s_addr = inet_addr(argv[1]);
// 向服务器发送数据
std::string message;
std::cout<<"请输入# ";
std::cin >> message;
sendto(sock, message.c_str(), message.size(), 0, (struct sockaddr *)&sever, sizeof(sever));
// 获取从服务器中返回的数据
struct sockaddr_in tmp;
#define NUM 1024
char buffer[NUM];
socklen_t len = sizeof(tmp);
recvfrom(sock, buffer, sizeof(buffer), 0, (struct sockaddr *)&tmp, &len);
std::cout << "sever返回的数据: " << buffer << std::endl;
}
return 0;
}
1. 客户端进程先创建并打开一个套接字文件。客户端不用去绑定IP和端口号,因为客户端是向服务器端发送数据的,所以在发送数据时操作系统会自动为客户端进程绑定IP和端口号。
2.客户端进程获取服务,接收到了服务器端进程返回的数据。
服务器端代码如下。
udp_sever.cc
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <cerrno>
#include <netinet/in.h>
#include <arpa/inet.h>
#include<string>
const uint16_t port = 8080;
int main()
{
// 1.创建socket,即打开一个文件
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0)
{
std::cout << "socket create erro" << errno << std::endl;
return 1;
}
// 2.绑定IP和端口号
struct sockaddr_in local;
// 当前socket通信方式为网络套接。
local.sin_family = AF_INET;
local.sin_port = htons(port);
// 绑定IP,需要将点分十进制IP转为32位整数IP地址,与此同时还要考虑大小端存储,
// 所以使用inet_addr()接口即可
// 但一般在服务器端,我们一般不显式绑定IP,因为在服务器端,可以认为一个服务器
// 可以有多个IP,所以服务器可以接收所有向该主机IP传递的数据,而不是只接收
// 给定的一个IP收到的数据
local.sin_addr.s_addr = INADDR_ANY; // 默认接收向该主机所有IP传递的数据
if (bind(sock, (const sockaddr *)&local, sizeof(local)) < 0)
{
std::cout << "bind err" << errno << std::endl;
return 2;
}
//提供服务
#define NUM 1024
char buffer[NUM];
while(1)
{
//1.接收从客户端发送的数据
struct sockaddr_in peer;
socklen_t len=sizeof(peer);
recvfrom(sock,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);
std::cout <<"#client发送的数据: "<<buffer << std::endl;
//2.获取到数据之后,向客户端发送返回的数据
std::string message="hello";
sendto(sock,message.c_str(),message.size(),0,(struct sockaddr*)&peer,len);
}
return 0;
}
1.服务器进程创建并打开一个套接字文件。
2.服务器端进程需要绑定IP和端口号,因为服务器端是接收数据的,所以必须绑定IP和端口号,只有这样,客户端进程才知道往哪个服务器进程发送数据。
3.创建服务,接收客户端进程发送的数据,并向客户端进程发送返回的数据。
运行结果如下。
运行结果符合预期。
以上便是本期的所有内容。
本期内容到此结束^_^