目录
一 、概念
(一)端口号概念
(二)套接字概念
(三)套接字(socket)编程接口
(四)sockaddr结构
(五)网络字节序
二、基于UDP的相关理解
(一)UDP协议
(二)编写简单的UDP服务端和客户端
(三)小结
三、基于TCP的相关理解
(一)TCP协议
(二)编写单执行流的TCP服务端和客户端
(三)编写多进程的TCP服务端和客户端
(四)编写多线程的TCP服务端和客户端
(五)小结
一 、概念
(一)端口号概念
- 端口号标定该主机上互联网中唯一一个进程。
- 进程ID是每个进程都有的,但是端口号只有是网络进程才给指派端口号。
- 一个端口号用来标识一个进程,一个进程可以绑定多个端口号。一个进程可以同时拥有端口和pid。
- 传输层协议(TCP和UDP)的数据段中有两个端口号, 分别叫做源端口号和目的端口号. 就是在描述 "数据是谁发的, 要发给谁"。
(二)套接字概念
- 所谓套接字(Socket),就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象,套接字编程本质是进程间通信。
- IP号标定唯一主机,IP+端口号(PORT,16位bit)标识全网内唯一一个进程,也叫套接字。
- 创建套接字,实际上是创建两套资源,第一套是通讯需要的网卡相关的资源,第二个是需要和文件系统挂钩的相关文件资源。
(三)套接字(socket)编程接口
- socket返回值是一个文件描述符,数值默认是3,一个进程可以打开多个套接字,描述套接字的数据结构struct socket,进程和套接字是用过文件描述符表关联起来。
// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
- int socket(int domain, int type, int protocol);
- domain相当于采用哪种协议一般选择ipv4,type 一般使用SOCK_STREM对应的是TCP (面向字节流的,面向链接的) SOCK_DFRM(面向用户数据报是UDP,无链接的)返回值是int型,成功返回一个文件描述符。
// 绑定端口号 (TCP/UDP, 服务器)
//所谓绑定就是在a.填充进当前服务器的ip地址和端口号b.不同套接字的操作方法不一样,他要初始化底层的函数指针让它指向不同的标准方法。
- int bind(int socket, const struct sockaddr *address,socklen_t address_len);
// 开始监听socket (TCP, 服务器)
- int listen(int socket, int backlog);
- backlog代表是底层链接队列长度是多少,一般不要设置太大。
// 接收请求 (TCP, 服务器)
- int accept(int socket, struct sockaddr* address,socklen_t* address_len);
- accept里的sockfd(lsock拉客的人)专注于从底层获取链接上来,返回值sockfd(服务员)专注于通信
// 建立连接 (TCP, 客户端)
- int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
(四)sockaddr结构
(五)网络字节序
- 默认网络跑的数据都是大端序列的。当发的是小端数据时,就需要转成大端。为了解决大小端问题,就引入网络字节序。网络是大端,本质是一种共识。
- 网络字节顺序是TCP/IP中规定好的一种数据表示格式,它与具体的CPU类型、操作系统等无关,从而可以保证数据在不同主机之间传输时能够被正确解释。网络字节顺序采用big-endian(大端)排序方式。
为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换:
- 头文件:#include<arpa/inet.h>
- htons把unsigned short类型从主机序转换到网络序。
- htonl 把unsigned long类型从主机序转换到网络序。
- ntohs 把unsigned short类型从网络序转换到主机序。
- ntohl 把unsigned long类型从网络序转换到主机序。
二、基于UDP的相关理解
(一)UDP协议
(二)编写简单的UDP服务端和客户端
//服务器 .hpp
#pragma once
#include <iostream>
#include <string>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
class udpServer{
private:
std::string ip;
int port;
int sock;
public:
udpServer(std::string _ip="127.0.0.1", int _port=8080)
:ip(_ip), port(_port)
{}
void initServer()
{
sock = socket(AF_INET, SOCK_DGRAM, 0);
std::cout << "sock: " << sock << std::endl;
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = inet_addr(ip.c_str());
if(bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0){
std::cerr << "bind error!\n" << std::endl;
exit(1);
}
}
void start()
{
char msg[64];
for(;;){
msg[0] = '\0';
struct sockaddr_in end_point;
socklen_t len = sizeof(end_point);
ssize_t s = recvfrom(sock, msg, sizeof(msg)-1,0, (struct sockaddr*)&end_point, &len);
if(s > 0){
msg[s] = '\0';
std::cout <<"client# " << msg << std::endl;
std::string echo_string = msg;
echo_string += " [server echo!]";
sendto(sock, echo_string.c_str(), echo_string.size(), 0, (struct sockaddr*)&end_point, len);
}
}
}
~udpServer()
{
close(sock);
}
};
//服务器端.cc
#include "udpClient.hpp"
int main()
{
udpClient uc;
uc.initClient();
uc.start();
return 0;
}
[lvhao@VM-0-12-centos lesson9]$ cat udpServer.cc
#include "udpServer.hpp"
int main()
{
udpServer *up = new udpServer();
up->initServer();
up->start();
delete up;
}
//客户端 .hpp
#pragma once
#include <iostream>
#include <string>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
class udpClient{
private:
std::string ip;
int port;
int sock;
public:
udpClient(std::string _ip="127.0.0.1", int _port=8080)
:ip(_ip), port(_port)
{}
void initClient()
{
sock = socket(AF_INET, SOCK_DGRAM, 0);
std::cout << "sock: " << sock << std::endl;
}
void start()
{
std::string msg;
struct sockaddr_in peer;
peer.sin_family = AF_INET;
peer.sin_port = htons(port);
peer.sin_addr.s_addr = inet_addr(ip.c_str());
for(;;){
std::cout << "Please Enter# ";
std::cin >> msg;
if(msg == "quit"){
break;
}
sendto(sock, msg.c_str(), msg.size(), 0, (struct sockaddr*)&peer, sizeof(peer));
char echo[128];
ssize_t s = recvfrom(sock, echo, sizeof(echo)-1, 0, nullptr, nullptr);
if(s >0){
echo[s] = 0;
std::cout << "server# " << echo << std::endl;
}
}
}
~udpClient()
{
close(sock);
}
};
//客户端.cc
#include "udpClient.hpp"
int main()
{
udpClient uc;
uc.initClient();
uc.start();
return 0;
}
//Makefile
.PHONY:all
all:udpClient udpServer
udpClient:udpClient.cc
g++ -o $@ $^ -std=c++11
udpServer:udpServer.cc
g++ -o $@ $^
.PHONY:clean
clean:
rm -f udpClient udpServer
(三)小结
- 想要删掉错输的信息按住ctrl+backspace。
- netstat -nlup查看绑定的ip和端口号。
- 服务器创建:第一步创建了套接字数据结构,如果没有ip和端口号就无法定位,无法定位就没办法通信,第二步需要在创建sockaddr_in数据结构从而填充协议族,在填充端口号,在填充ip地址,进而在进行相关动作的一个绑定。
- 服务器端的ip和port不能轻易被更改,必须是确定的,众所周知的。http:80,https:443 ssh:22 MySQL:3306。
- 客户端给别人发消息就必须知道别人的ip和端口号,这就需要创捷结构填入别人的ip和端口号。
- 127.0.0.1本地环回,通常用来进行网络通信代码的本地测试,一般跑通说明代码在本地环境以及代码基本没有大问题。
- 服务器绑定ip为0时等价于绑定公网ip,一般不会这么写。
- 客户端不需要强bind(绑定)
a.你在进行bind的时候,很容易冲突!客户端无法启动!
b.客户端需要唯一性,但是不需要明确必须是哪个端口号。但是需要ip和port(端口)。client udp,recv和cend,系统会自动进行ip和端口号的绑定!
三、基于TCP的相关理解
(一)TCP协议
- TCP属于传输层的传输控制协议,比较复杂,管的事多,而UDP则较为简单。
- 传输层协议
- 有连接
- 可靠传输
- 面向字节流
(二)编写单执行流的TCP服务端和客户端
//服务器hpp
#pragma once
#include <iostream>
#include <unistd.h>
#include <cstdlib>
#include <string>
#include <cstdlib>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define BACKLOG 5
class tcpServer{
private:
int port;
int lsock; //监听套接字
public:
tcpServer(int _port)
:port(_port), lsock(-1)
{}
void initServer()
{
lsock = socket(AF_INET, SOCK_STREAM, 0);
if(lsock < 0){
std::cerr << "socket error" << std::endl;
exit(2);
}
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(lsock, (struct sockaddr*)&local, sizeof(local)) < 0){
std::cerr << "bind error" << std::endl;
exit(3);
}
if(listen(lsock, BACKLOG) < 0){
std::cerr << "bind error" << std::endl;
exit(4);
}
}
void service(int sock)
{
char buffer[1024];
while(true){
//read or write -> ok!
size_t s = recv(sock, buffer, sizeof(buffer)-1, 0);
if(s > 0){
buffer[s] = 0;
std::cout << "client# " << buffer << std::endl;
send(sock, buffer, strlen(buffer), 0);
}
else if(s == 0){
std::cout << "client quit ...." << std::endl;
break;
}
else {
std::cout << "recv client data error..." << std::endl;
break;
}
}
close(sock);
}
void start()
{
sockaddr_in endpoint;
while(true){
socklen_t len = sizeof(endpoint);
int sock = accept(lsock, (struct sockaddr*)&endpoint, &len);
if(sock < 0){
std::cerr << "accept error" << std::endl;
continue;
}
std::string cli_info = inet_ntoa(endpoint.sin_addr);
cli_info += ":";
cli_info += std::to_string(ntohs(endpoint.sin_port));
std::cout << "get a new link ..." << cli_info << " sock: " << sock <<std::endl;
service(sock);
}
}
~tcpServer()
{}
};
//服务器.cc
#include "tcpServer.hpp"
static void Usage(std::string proc)
{
std::cout << "Usage: " << std::endl;
std::cout << '\t' << proc << " port" << std::endl;
}
int main(int argc, char *argv[])
{
if(argc != 2){
Usage(argv[0]);
exit(1);
}
tcpServer * tp = new tcpServer(atoi(argv[1]));
tp->initServer();
tp->start();
delete tp;
return 0;
}
//客户端 .hpp
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
class tcpClient{
private:
std::string svr_ip;
int svr_port;
int sock;
public:
tcpClient(std::string _ip="127.0.0.1", int _port = 8080)
:svr_ip(_ip),svr_port(_port)
{}
void initClient()
{
sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock < 0){
std::cerr << "socket error" << std::endl;
exit(2);
}
struct sockaddr_in svr;
svr.sin_family = AF_INET;
svr.sin_port = htons(svr_port);
svr.sin_addr.s_addr = inet_addr(svr_ip.c_str());
if(connect(sock, (struct sockaddr*)&svr, sizeof(svr)) != 0){
std::cerr << "connect error" << std::endl;
}
//connect success;
}
void start()
{
char msg[64];
while(true){
std::cout << "Please Enter Message# ";
fflush(stdout);
size_t s = read(0, msg, sizeof(msg)-1);
if(s > 0){
msg[s-1] = 0;
send(sock, msg, strlen(msg), 0);
size_t ss = recv(sock, msg, sizeof(msg)-1, 0);
if(ss > 0){
msg[ss] = 0;
std::cout << "server echo # " << msg << std::endl;
}
}
}
}
~tcpClient()
{
close(sock);
}
};
//客户端 .cc
#include "tcpClient.hpp"
static void Usage(std::string proc)
{
std::cout << "Usage: \n" << "\t";
std::cout << proc << " svr_ip svr_port" << std::endl;
}
int main(int argc, char *argv[])
{
if(argc != 3){
Usage(argv[0]);
exit(1);
}
tcpClient *tc = new tcpClient(argv[1], atoi(argv[2]));
tc->initClient();
tc->start();
delete tc;
return 0;
}
FLAG=-std=c++11
.PHONY:all
all:tcpClient tcpServer
tcpClient:tcpClient.cc
g++ -o $@ $^ $(FLAG) -static
tcpServer:tcpServer.cc
g++ -o $@ $^ $(FLAG)
.PHONY:clean
clean:
rm -f tcpClient tcpServer
(三)编写多进程的TCP服务端和客户端
//服务器 .hpp
#pragma once
#include <iostream>
#include <unistd.h>
#include <cstdlib>
#include <string>
#include <cstdlib>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#define BACKLOG 5
class tcpServer{
private:
int port;
int lsock; //监听套接字
public:
tcpServer(int _port)
:port(_port), lsock(-1)
{}
void initServer()
{
//设置信号 为默认忽略,当子进程要退出时发送信号,系统默认回收。
signal(SIGCHLD, SIG_IGN);
lsock = socket(AF_INET, SOCK_STREAM, 0);
if(lsock < 0){
std::cerr << "socket error" << std::endl;
exit(2);
}
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(lsock, (struct sockaddr*)&local, sizeof(local)) < 0){
std::cerr << "bind error" << std::endl;
exit(3);
}
if(listen(lsock, BACKLOG) < 0){
std::cerr << "bind error" << std::endl;
exit(4);
}
}
void service(int sock)
{
char buffer[1024];
while(true){
size_t s = recv(sock, buffer, sizeof(buffer)-1, 0);
if(s > 0){
buffer[s] = 0;
std::cout << "client# " << buffer << std::endl;
send(sock, buffer, strlen(buffer), 0);
}
else if(s == 0){
std::cout << "client quit ...." << std::endl;
break;
}
else {
std::cout << "recv client data error..." << std::endl;
break;
}
}
close(sock);
}
void start()
{
sockaddr_in endpoint;
while(true){
socklen_t len = sizeof(endpoint);
int sock = accept(lsock, (struct sockaddr*)&endpoint, &len);
if(sock < 0){
std::cerr << "accept error" << std::endl;
continue;
}
std::string cli_info = inet_ntoa(endpoint.sin_addr);
cli_info += ":";
cli_info += std::to_string(ntohs(endpoint.sin_port));
std::cout << "get a new link ..." << cli_info << " sock: " << sock <<std::endl;
pid_t id = fork();
//child
if(id == 0){
close(lsock);
service(sock);
exit(0);
}
close(sock);
}
}
~tcpServer()
{}
};
//服务器.cc
#include "tcpServer.hpp"
static void Usage(std::string proc)
{
std::cout << "Usage: " << std::endl;
std::cout << '\t' << proc << " port" << std::endl;
}
int main(int argc, char *argv[])
{
if(argc != 2){
Usage(argv[0]);
exit(1);
}
tcpServer * tp = new tcpServer(atoi(argv[1]));
tp->initServer();
tp->start();
delete tp;
return 0;
}
//客户端.hpp
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
class tcpClient{
private:
std::string svr_ip;
int svr_port;
int sock;
public:
tcpClient(std::string _ip="127.0.0.1", int _port = 8080)
:svr_ip(_ip),svr_port(_port)
{}
void initClient()
{
sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock < 0){
std::cerr << "socket error" << std::endl;
exit(2);
}
struct sockaddr_in svr;
svr.sin_family = AF_INET;
svr.sin_port = htons(svr_port);
svr.sin_addr.s_addr = inet_addr(svr_ip.c_str());
if(connect(sock, (struct sockaddr*)&svr, sizeof(svr)) != 0){
std::cerr << "connect error" << std::endl;
}
}
void start()
{
char msg[64];
while(true){
std::cout << "Please Enter Message# ";
fflush(stdout);
size_t s = read(0, msg, sizeof(msg)-1);
if(s > 0){
msg[s-1] = 0;
send(sock, msg, strlen(msg), 0);
size_t ss = recv(sock, msg, sizeof(msg)-1, 0);
if(ss > 0){
msg[ss] = 0;
std::cout << "server echo # " << msg << std::endl;
}
else if(ss == 0){
break;
}
else{
break;
}
}
}
}
~tcpClient()
{
close(sock);
}
};
//客户端.cc
#include "tcpClient.hpp"
static void Usage(std::string proc)
{
std::cout << "Usage: \n" << "\t";
std::cout << proc << " svr_ip svr_port" << std::endl;
}
int main(int argc, char *argv[])
{
if(argc != 3){
Usage(argv[0]);
exit(1);
}
tcpClient *tc = new tcpClient(argv[1], atoi(argv[2]));
tc->initClient();
tc->start();
delete tc;
return 0;
}
(四)编写多线程的TCP服务端和客户端
//服务器.hpp
#pragma once
#include <iostream>
#include <unistd.h>
#include <cstdlib>
#include <string>
#include <cstdlib>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <pthread.h>
#define BACKLOG 5
class tcpServer{
private:
int port;
int lsock; //监听套接字
public:
tcpServer(int _port)
:port(_port), lsock(-1)
{}
void initServer()
{
signal(SIGCHLD, SIG_IGN);
lsock = socket(AF_INET, SOCK_STREAM, 0);
if(lsock < 0){
std::cerr << "socket error" << std::endl;
exit(2);
}
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(lsock, (struct sockaddr*)&local, sizeof(local)) < 0){
std::cerr << "bind error" << std::endl;
exit(3);
}
if(listen(lsock, BACKLOG) < 0){
std::cerr << "bind error" << std::endl;
exit(4);
}
}
static void service(int sock)
{
char buffer[1024];
while(true){
size_t s = recv(sock, buffer, sizeof(buffer)-1, 0);
if(s > 0){
buffer[s] = 0;
std::cout << "client# " << buffer << std::endl;
send(sock, buffer, strlen(buffer), 0);
}
else if(s == 0){
std::cout << "client quit ...." << std::endl;
break;
}
else {
std::cout << "recv client data error..." << std::endl;
break;
}
}
close(sock);
}
static void *serviceRoutine(void *arg)
{
pthread_detach(pthread_self());
std::cout << "create a new thread for IO" << std::endl;
int *p = (int*)arg;
int sock = *p;
service(sock);
delete p;
}
void start()
{
sockaddr_in endpoint;
while(true){
socklen_t len = sizeof(endpoint);
int sock = accept(lsock, (struct sockaddr*)&endpoint, &len);
if(sock < 0){
std::cerr << "accept error" << std::endl;
continue;
}
std::string cli_info = inet_ntoa(endpoint.sin_addr);
cli_info += ":";
cli_info += std::to_string(ntohs(endpoint.sin_port));
std::cout << "get a new link ..." << cli_info << " sock: " << sock <<std::endl;
pthread_t tid;
int *p = new int(sock);
pthread_create(&tid, nullptr, serviceRoutine, (void*)p);
}
}
~tcpServer()
{}
};
//服务器.cc
#include "tcpServer.hpp"
static void Usage(std::string proc)
{
std::cout << "Usage: " << std::endl;
std::cout << '\t' << proc << " port" << std::endl;
}
int main(int argc, char *argv[])
{
if(argc != 2){
Usage(argv[0]);
exit(1);
}
tcpServer * tp = new tcpServer(atoi(argv[1]));
tp->initServer();
tp->start();
delete tp;
return 0;
}
//客户端.hpp
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
class tcpClient{
private:
std::string svr_ip;
int svr_port;
int sock;
public:
tcpClient(std::string _ip="127.0.0.1", int _port = 8080)
:svr_ip(_ip),svr_port(_port)
{}
void initClient()
{
sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock < 0){
std::cerr << "socket error" << std::endl;
exit(2);
}
struct sockaddr_in svr;
svr.sin_family = AF_INET;
svr.sin_port = htons(svr_port);
svr.sin_addr.s_addr = inet_addr(svr_ip.c_str());
if(connect(sock, (struct sockaddr*)&svr, sizeof(svr)) != 0){
std::cerr << "connect error" << std::endl;
}
}
void start()
{
char msg[64];
while(true){
std::cout << "Please Enter Message# ";
fflush(stdout);
size_t s = read(0, msg, sizeof(msg)-1);
if(s > 0){
msg[s-1] = 0;
send(sock, msg, strlen(msg), 0);
size_t ss = recv(sock, msg, sizeof(msg)-1, 0);
if(ss > 0){
msg[ss] = 0;
std::cout << "server echo # " << msg << std::endl;
}
else if(ss == 0){
break;
}
else{
break;
}
}
}
}
~tcpClient()
{
close(sock);
}
};
//客户端.cc
#include "tcpClient.hpp"
void Usage(std::string proc)
{
std::cout << "Usage: \n" << "\t";
std::cout << proc << " svr_ip svr_port" << std::endl;
}
int main(int argc, char *argv[])
{
if(argc != 3){
Usage(argv[0]);
exit(1);
}
tcpClient *tc = new tcpClient(argv[1], atoi(argv[2]));
tc->initClient();
tc->start();
delete tc;
return 0;
}
(五)小结
- 工程上一般很少使用ip,只使用端口号,一般填INADDR_ANY(可以理解代表该机器的全部IP),这个宏表示本地任意IP地址,因为服务器可能有多个网卡,每个网卡也可能绑定多个IP地址,这样设置可以在所有的IP地址上监听,知道与某个客户端建立了连接时才确定下来到底用哪个IP地址。
- tcp绑定后,必须让tcp设置成监听状态,TCP允许在任何时刻链接我,不连接不能进行发送但是UDP可以,所以UDP叫做无链接。UDP相当于标定了IP和端口号,并没有进行连接他俩。
- 192.168.1.2.3是字符串风格的四字节 点分十进制方案。inet_ntoa四字节IP转换成点分十进制。inet_addr四字节字符串风格的IP地址转化成四字节网络序列IP。
- telnet登录一个服务器所需要的命令要进行交互输入ctrl+上尖括号。
- +static代码只要系统一样,代码则不依赖任何第三方库就可以跑。
- 客户端和服务器都需要-std=c++11时我们需要FLAG=-std=c++11; 后面直接$(FLAG);
- sz -E+文件名+rz 就是从linux发送到windows。
- cp -rf +需要拷贝的文件夹+拷贝成那个名字的文件夹在当前路径拷贝一个文件夹。