文章目录
- 铺垫一下概念知识
- 基于UDP协议下的套接字编程
- 基于TCP协议下的套接字编程
铺垫一下概念知识
我们知道IP地址是用来标识主机唯一性的。
而源IP地址表示从哪个主机来, 目的IP地址表示去哪个主机。
端口号(port):
1,标识进程唯一性的。也就是说,一个端口号用来标识一个进程。
2,端口号是一个2字节16位的整数。
同样的:多个端口号可以对应一个进程, 多个进程不能对应一个端口。
因为必须通过端口号找到唯一一个进程。
IP地址 + 端口号(port) 就可以表示互联中的一台主机中的一个进程。
源端口号和目的端口号:传输层的数据段中有两个端口号,分别叫做目的端口号和源端口号,表示数据是发给哪个进程,
由哪个进程发的
我们知道,网络通信的本质就是进程间通信。
TCP协议:
1,传输层协议
2,有链接
3,可靠
4,面向字节流(数据从一方流向另一方)。
UDP协议
1,传输层协议
2,无连接
3,不可靠
4,面向数据报。
这里的可靠和不可靠是中性词,可靠肯定是要更多资源去维护的。如银行取钱那肯定是要可靠的。
其他的不一定要可靠。
网络字节序的概念:
其实在内存中分大小端。
小端:数据的低位存放在低地址
大端:数据的低位存放在高地址
TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节。
发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出
如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可。
网络字节序和主机字节序的相关转换函数:
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
h----host主机, n-----network网络。
基于UDP协议下的套接字编程
UDP协议下的相关函数操作
服务端:
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <netinet/in.h> //这两个头文件是 struct sockaddr_in
#include <arpa/inet.h>
using namespace std;
void Usage(const char* proc)
{
cout << "Usage:" << endl << "./server " << "server.port";
}
// ./server server.port
int main(int argc, char* argv[])
{
if(argc != 2)
{
Usage(argv[0]);
return 1;
}
//1 创建套接字(网络文件), sock---文件描述符
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if(sock < 0)
{
cout << "sock fail" << endl;
return 2;
}
//2 bind 绑定ip地址和端口号port
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(atoi(argv[1]));
local.sin_addr.s_addr = INADDR_ANY; //服务端可以任意绑定
if(bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0)
{
cout << "bind fail" << endl;
return 3;
}
//提供服务
char buff[1024];
while(1)
{
struct sockaddr_in peer;
memset(&peer, 0 , sizeof(peer));
socklen_t len = sizeof(peer);
//peer 返回的客户端的ip + 端口号的属性集合 sockaddr
ssize_t s = recvfrom(sock, buff, sizeof(buff) , 0, (struct sockaddr*)&peer, &len);
if(s > 0)
{
//成功接受
buff[s] = 0;
cout << "clinet #" << buff << endl;
const char* ch = "我是服务器, 你好啊";
sendto(sock, ch, strlen(ch), 0, (struct sockaddr*)&peer, len);
}
else
{
cout << "recvfrom fail" << endl;
return 3;
}
}
return 0;
}
客户端:
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <netinet/in.h> //这两个头文件是 struct sockaddr_in
#include <arpa/inet.h>
using namespace std;
void Usage(const char* proc)
{
cout << "Usage" << endl << proc << "server.ip server.port" << endl;
}
// ./client server.ip server.port
int main(int argc, char* argv[])
{
if(argc != 3)
{
Usage(argv[0]);
return 1;
}
//1 创建套接字
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if(sock < 0)
{
cout << "sock fail" << endl;
return 1;
}
//不需要绑定,OS会自动绑定
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(atoi(argv[2])); //端口号
server.sin_addr.s_addr = inet_addr(argv[1]); //ip
while(1)
{
cout << "client:" ;
char line[1024];
cin >> line;
sendto(sock, line, sizeof(line), 0, (struct sockaddr*)&server, sizeof(server));
//ret 占位用,无其他作用
struct sockaddr_in ret;
socklen_t len = sizeof(ret);
char buff[1024];
ssize_t s = recvfrom(sock, buff, sizeof(buff), 0, (struct sockaddr*)&ret, &len);
if(s < 0)
{
cout << "client recvfron fail" << endl;
return 4;
}
cout << "server say" << buff << endl << endl;;
}
return 0;
}
基于TCP协议下的套接字编程
相关操作函数的总结:
1,创建socket的过程,socket()本质是创建网络文件,与系统相关
2,bind() sockaddr_in ----> 填入ip + port 本质是将ip + port 与网络文件相关联
3, listen() 设置该socket文件的状态,允许客户端来链接我
4 ,accept() 获取新连接到应用层,以文件描述符为代表,OS中肯定会存在大量的链接,如何管理呢?—>先描述,再组织
5, read/write 本质就是进行网络通信,而对于用户来说,就是在进行文件的正常读写。
6, close() ,关闭文件, 系统层面上,释放曾经申请的文件资源和链接资源
7, connect() 本质是发起链接, 在系统层面上,构建一个报文发过去。
客户端:
#include <string>
#include <cstring>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <iostream>
#include <signal.h>
#include <pthread.h>
#include <sys/wait.h>
using namespace std;
void Usage(const char* proc)
{
cout << "Usage:" << endl << proc << " port" << endl;
}
void ServerIO(int new_sock)
{
//开始服务
while(1)
{
char buff[1024];
memset(buff, 0, sizeof(buff));
ssize_t s = read(new_sock, buff, sizeof(buff));
if(s > 0)
{
cout << "client say#" << buff << endl;
const char* ch = "server recv";
write(new_sock, ch, strlen(ch));
}
else if(s == 0)
{
//对端关闭
}
else
{
cout << "new_sock fail " << endl;
return ;
}
}
}
void* handler(void* args)
{
int* p = (int*)args;
pthread_detach(pthread_self());
ServerIO(*p);
close(*p);
}
int main(int argc, char* argv[])
{
if(argc != 2)
{
Usage(argv[0]);
return 1;
}
//1, 创建套接字(网络文件) , AF_INFT是ipv4, sock_stream 是面向字节流
int sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock < 0)
{
cout << "sock fail" << endl;
return 2;
}
//2 bind
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(atoi(argv[1]));
local.sin_addr.s_addr = INADDR_ANY;
if(bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0)
{
cout << "bind error" << endl;
return 3;
}
//3 监听
if(listen(sock,5) < 0)
{
cout << "listen fail" << endl;
return 4;
}
//4 提供服务
while(1)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
//new sock 是提供服务的套接字, sock是用于监听的套接字
// 提供服务 在线拉客
int new_sock = accept(sock, (struct sockaddr*)&peer, &len);
if(new_sock < 0)
{
continue;
}
//通过创建进程来实现可对多个客户同时来进行服务
/*signal(SIGCHLD, SIG_IGN);
pid_t id = fork();
if(id == 0)
{
//子进程关闭与自己无关的文件描述符
close(sock);
ServerIO(new_sock);
close(new_sock);
}
else if(id < 0)
{
return 7;
}
else
{
//panrent
}
*/
//通过创建线程的方式来实现对多个客户同时进行服务
pthread_t tid;
int* pram = new int(new_sock);
pthread_create(&tid, nullptr, handler, pram);
}
return 0;
}
用户端:
#include <string>
#include <cstring>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <iostream>
using namespace std;
void Usage(const char* proc)
{
cout << "Usage:" << endl << proc << " port" << endl;
}
int main(int argc, char* argv[])
{
if(argc != 2)
{
Usage(argv[0]);
return 1;
}
//1, 创建套接字(网络文件) , AF_INFT是ipv4, sock_stream 是面向字节流
int sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock < 0)
{
cout << "sock fail" << endl;
return 2;
}
//2 bind
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(atoi(argv[1]));
local.sin_addr.s_addr = INADDR_ANY;
if(bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0)
{
cout << "bind error" << endl;
return 3;
}
//3 监听
if(listen(sock,5) < 0)
{
cout << "listen fail" << endl;
return 4;
}
//4 提供服务
while(1)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
//new sock 是提供服务的套接字, sock是用于监听的套接字
// 提供服务 在线拉客
int new_sock = accept(sock, (struct sockaddr*)&peer, &len);
if(new_sock < 0)
{
continue;
}
//开始服务
while(1)
{
char buff[1024];
memset(buff, 0, sizeof(buff));
ssize_t s = read(new_sock, buff, sizeof(buff));
if(s > 0)
{
cout << "client say#" << buff << endl;
const char* ch = "server recv";
write(new_sock, ch, strlen(ch));
}
else if(s == 0)
{
//对端关闭
}
else
{
cout << "new_sock fail " << endl;
return 6;
}
}
}
return 0;
}