网络----------------------网络编程套接字(认识ip 、port,实现简单的UDP/TCP客户端服务器)

套接字编程预备知识

1、套接字原理

1)网络编程套接字也是实现进程间通信的方式,我们知道进程间通信一般需要有一个条件就是两个进程需要看到同一份资源,然后进程1对这份资源进行写操作,进程2读这份资源就能知道进程1说了什么,基本上就实现了进程通信。但是不同的是,socket(套接字)不仅可以实现本机内不同进程间的通信,也可以实现网络中两台不同主机的通信。
2)我们知道网络中通信依赖的是网卡,那么我们就可以将socket抽象理解成网卡(Linux中一切皆文件),往网卡中写再从网卡中读,就能实现数据传输。
3)套接字是由端口号和ip地址等信息构成的

2、认识IP地址

由于UDP在IP协议的上一层,所以也实现了IP协议,我们就来认识一下IP地址,IP协议有两个版本(IPv4和IPv6,通常默认情况下是前者),ip地址用来标识网络中不同主机的地址;对于IPv4来说,ip地址是一个4字节,32位的整数

ip地址的表示

通常使用“点分十进制”的字符串表示ip地址,例如192.168.128.116;用.分割的每一部分都是一个字节,范围0~255

3、认识端口号

端口号的结构

  • 端口号是一个两字节16位的整数;
  • 端口号要用来标识一个进程,告诉操作系统,当前数据交个哪个进程处理
  • ip地址+端口号能够标识网络上的某一台主机的某一个进程
  • 一个端口号只能被一个进程占用

分辨端口号和进程ID

一个进程可以绑定多个端口号,但是一个端口号只能被一个进程绑定,所以端口号确定对应的进程也就确定了。
上述情况只是一般情况,特殊情况下,像父进程fork()后创建了子进程,那么子进程会拷贝父进程的代码和数据(包括文件描述符,所以也包含socket),这样的话就有两个进程绑定同一个端口号了。

4、认识UDP协议和TCP协议

1)UDP协议

UDP协议全称是用户数据报协议,和TCP一样用于处理数据包,但是它是一种无连接的协议,处于OSI模型中的传输层,其特点总结如下:

1、传输层协议

负责数据传输和数据控制。对会话层等高三层提供可靠的目的站点。

2、无连接

类比发微信,只要是好友连网的情况下就能发送出去,不需要说先向对方发送请求建立连接才能发送消息。

3、不可靠传输

没有确认机制,不能确定对方是否听懂或者说收到,不能确定传输过程中数据是否完整。

4、面向数据报

发送过来100字节的数据必须一次性读完否则没读的数据再也读不到了。

2)TCP协议

1、传输层协议

2、有连接

必须在双方建立好连接后,才能实现真正的收发消息

3、可靠传输

类比打电话的例子,可靠传输意思就是指对方接通后说话,我们这边就感知到数据传送成功。

4、面向字节流

接收到10字节,我们可以一次读一个字节,分10次来读,也可以一次读10个字节,一次性读完,在此期间,没有读的书据不会消失,还能读的到。

5、网络字节序(相当于大端序)

网络层数据传输,必须是大端序

我们知道通常存储数据都在磁盘上或者内存中,内存中有大端和小端之分,磁盘中也有,同样的网络字节流也是有大小端之分的,就用网络字节序来规定:

  • 发送主机通常将缓冲区中的数据按内存地址由低到高的顺序发出。
  • 接收主机把从网络上接收到的字节依次保存在缓冲区(由低到高的地址顺序)。
  • 网络字节流规定先发出的数据是低地址,后发出的相对而言是高地址。
  • TCP/IP协议规定,网络字节流采用大端序(低地址高字节);
  • 所以如果当前发送主机是小端,就要进行转换再发送,同样的接收后在往显示器上写,就要在转换成小端。

涉及到的字节序转换函数

以下函数只对于主机序是小端字节序起作用,如果是大端字节序对于数据不做转换。

#include<arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostlong);//前两种都是主机序转成网络序,l表示长整数,s表示短整数
uint32_t ntohl(uint32_t hostlong);
uint16_t ntohl(uint16_t hostlong);//这两种是网络序转成主机序

6、简单实现UDP网络程序

认识socket编程接口

掌握socket常见API(重要掌握)

1、创建socket文件描述符(TCP/UDP,客户端+服务器)
int socket(int domain,int type,int protocol);
//参数1是指定协议类型(ipv4或者ipv6);参数2是指传输数据方式(面向数据报还是面向字节流);参数3默认为02、绑定端口号(TCP/UDP,服务器)
int bind(int socket,const struct sockaddr* address,socklen_t address_len);
//参数1socket文件描述符,参数2是绑定到本机的端口号和地址,参数3是参数2结构体的大小
3、监听socket(TCP,服务器)
只有监听成功才能允许客户端连接。
int listen(int socket,int backlog);//backlog相当于等待的服务器,只有多一点才有可能有空闲的处理请求
4、接收请求(TCP,服务器)
int accept(int socket,struct sockaddr* address,socklen_t* address_len);
5、建立连接(TCP,客户端)
int connect(int sockfd,const struct sockaddr* addr,socklen_t addrlen);

认识socketaddr结构

这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

具体实现

server.c要做的事情:

  • 我们利用操作命令行参数,让指定的主机运行工作。所以首先校验命令行参数
  • 1、初始化服务器分两步:创建好socket文件;将socket文件绑定到指定的结构体sockaddr中
  • 2、开始事务的循环:
  • 1)接收来自客户端的请求
  • 2)处理请求,并将处理好的结构返回给客户端(此处是回显式服务器-客户端,所以直接将请求打印到显示器)
    // ./server 127.0.0.1 9090
#include<stdio.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<string.h>

int main(int argc,char* argv[]){
if(argc!=3){
printf("Usage :./server[ip][port]\n");
return 1;
}
int sockfd=socket(AF_INET,SOCK_DGRAM,0);
if(sockfd<0){
perror("socket error\n");
return 2;
}

struct sockaddr_in local_addr;
local_addr.sin_family=AF_INET;
local_addr.sin_port=htons(atoi(argv[2]));
local_addr.sin_addr.s_addr=inet_addr(argv[1]);
int ret=bind(sockfd,( struct sockaddr*)&local_addr,sizeof(local_addr));
if(ret<0){
perror("bind\n");
return 1;
}

char buf[1024]={0};
struct sockaddr_in peer;
while(1){
int len=sizeof(peer);
ssize_t read_size=recvfrom(sockfd,buf,sizeof(buf)-1,0,(struct sockaddr*)&peer,&len);
if(read_size<0){
continue;
}
else if(read_size>0){
buf[read_size]='\0';
printf("[%s:%d]:%s\n",inet_ntoa(peer.sin_addr),ntohs(peer.sin_port),buf);
sendto(sockfd,buf,strlen(buf),0,(struct sockaddr*)&peer,sizeof(peer));
}
}
return 0;
}

client.c要做的事情:

-1、 首先也是检查命令行参数
- 2、创建socket文件,此时需要创建一个对端sockaddr结构体,作为发送函数的参数。
- 3、发送请求给服务器
-4、 接受服务器的响应并且打印出来

#include<stdio.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<string.h>

int main(int argc,char* argv[]){

int sockfd=socket(AF_INET,SOCK_DGRAM,0);
if(sockfd<0){
perror("socket\n");
}
struct sockaddr_in server_addr;
server_addr.sin_family=AF_INET;
server_addr.sin_port=htons(atoi(argv[2]));
server_addr.sin_addr.s_addr=inet_addr(argv[1]);
char buf[1024]={0};
while(1){
fflush(stdout);
ssize_t read_size= read(0,buf,sizeof(buf)-1);
if(read_size<0){
perror("read\n");
return 1;
}
else if(read_size==0){
return 0;
}
else{
buf[read_size-1]=0;
sendto(sockfd,buf,strlen(buf),0,(struct sockaddr*)&server_addr,sizeof(server_addr));
ssize_t recvsize=recvfrom(sockfd,buf,sizeof(buf)-1,0,NULL,NULL);
if(recvsize<=0){
perror("recvfrom\n");
return 1;
}
else{
buf[recvsize]=0;
printf("server say:%s\n",buf);
}
}
}
return 0;
}

7、简单的TCP网络程序

具体实现

8、TCP和UDP对比

TCP UDP
可靠传输 不可靠传输
有连接 无连接
字节流 数据报

猜你喜欢

转载自blog.csdn.net/cx2479750196/article/details/80756420