文章目录
前言
socket编程是一门网络编程技术,在主要的网络通信中都会使用到它,可以使用socket编程来实现计算机之间的通信。
一、Internet历史
1968:ARPAnet(阿帕网) 采用的协议 NCP(网络控制协议). internet的雏形
1.不能跨越系统,与平台.
2.不能对数据就行纠错.
1974: 第一份TCP(传输控制协议)协议详细说明.
1.协议在有数据包丢失时不能有效的纠正
1983:TCP/ip协议成为Internet的"世界语"
把之前的tcp协议分成两部分:
IP: 用于在互联网环境中找寻目标主机.
TCP: 对数据进行纠错.
二、网络体系结构
1.OSI模型
7 层协议模型 理想化模型.
1.应用层
2.表示层
3.会话层
4.传输层
5.网路层
6.数据链路层
7.物理层
2.TCP/IP模型
4 层协议模型 标准模型
{
1.应用层:
作用:决定上层用户处理数据的方式
http(超文本传输协议),FTP(文件传输协议),SMTP(邮件传输协议)
2.传输层:
作用:提供端到端的连接(端口作用:标识进程)
TCP(传输控制协议)
UDP(用户数据报协议)
单位:数据包.
3.网络层:
作用:提供数据包的路由. (标识主机)
IP(网间协议): 网络环境中起到路由的作用(找目标主机)
ICMP(互联网控制信息协议):
IGMP:
单位:分组形式.
4.网络接口层(数据链路与物理层):
作用:物理层面的通信(介质之间),
传输有地址的帧以及错误检测功能
以太网协议.
物理层单位: 二进制流方式 位bit
数据链路:数据帧.
}
三、网络设计框架
{
c/s: 客户端 与 服务器
缺点:1.开发工作量大,调试麻烦.
2.用户安全隐患大.
优点:1.本地可以缓存数据.(技能特效)
2.协议比较灵活(可以自己制定)
b/s: 浏览器 与 服务器
优点:1.开发工作量小,调试较简单.
2.用户安全隐患小.
缺点:1.本地不可以缓存数据.
2.协议固定是http(要包含所有http协议内容)
}
四、TCP服务器编写流程
{
基础:
套接字:1.是一个网络通信的接口.
2.是一种特殊文件描述符(非负整数).
套接字类型:SOCK_STREAM: 流式套接字. 以二进制流方式依次发送.
保证数据可靠
默认采用tcp协议
SOCK_DGRAM: 数据报套接字.
不保证数据可靠
默认采用udp协议.
SOCK_RAW: 原始套接字.
流程:
1.创建套接字–socket() ----> 买手机
int socket(int domain, int type, int protocol);
{
作用:创建一个用于网络通信的套接字.
domain:
AF_UNIX: 本地套接字协议(创建出的套接字只能本地通信)
AF_INET: 选择ipv4协议族.
AF_INET6: 选择ipv6协议族.
IPv4:表示ip地址由32位二进制数据构成
IPv6:表示ip地址由128位二进制数据构成
type:
SOCK_STREAM: 流式套接字
SOCK_DGRAM: 数据报套接字
protocol: 通常该参数都是以缺省方式 0 传参.
例如: 第二个参数选择SOCK_STREAM,改参数传0
代表采用SOCK_STREAM的默认协议(TCP)
返回值:真确返回文件描述符(套接字)
错误 -1
}
2.绑定套接字 bind() ----> 买卡以及与手机进行绑定
1.设置IP地址(标识主机) 2.设置端口号(标识进程)
struct sockaddr_in: 结构体作用 用于设置ipv4协议族 的ip地址与端口.
struct sockaddr_in{
sa_family_t sin_family; // 选择协议族 AF_INET
in_port_t sin_port; // unsigned short 设置端口号.
struct in_addr sin_addr; // 结构体ip地址 要访问s_addr来进行设置
};
struct in_addr {
uint32_t s_addr; //s_addr 是ip地址的二进制形式.
};
ip地址形式:点分十进制"192.168.2.2".这种形式是给用户看的计算机不认识
二进制形式 11000000 10101000 00000010 00000010
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
{
sockfd: socket函数正确返回的套接字
addr: 标准地址信息结构体.(用户使用的结构体都要强转为该结构体)
addrlen: 地址信息结构体长度. sizeof(seraddr).
返回值: 成功返回 0
错误返回 -1.
}
端口:标识进程 系统占用:0 ~ 1023
用户一般可使用 1024 ~ 65535
ifconfig:查看网络信息.
3.监听listen() -----> 等待电话
int listen(int sockfd, int backlog);
{
作用:监听客户端.
sockfd: socket正确返回的套接字.
backlog: 同一时间能够监听的客户端个数.(不是代表同时能连多少个客户端)
返回值:成功返回 0
错误返回 -1.
}
4.接受客户端请求 accept(). ----> 接听电话
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
{
作用:接受客户端请求
sockfd: socket函数正确返回的套接字
addr: 获取客户端地址信息. 如果不需要则传 NULL.
addrlen: 客户端地址信息长度. 如果不需要则传 NULL.
返回值: 正确返回一个通信套接字(连接套接字). 重点!
也是一个文件描述符.
与客户端的通信用通信套接字来实现.socket返回的值可以称为监听套接字
错误 -1
}
5.数据的收发. 因为是文件描述符所以 —> 通话过程
read()/write().可以表示收发
send()/recv() 也表示收发. 一定要配套使用.
6.关闭套接字. close(). ----> 挂电话
}
创建TCP服务器示例代码:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#define err_log(log) do{perror(log);exit(-1);}while(0)
int main(int argc, char *argv[])
{
/*1.创建套接字*/
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd == -1)
{
err_log("socket");
}
printf("socket-----success\n");
/*2.绑定套接字*/
struct sockaddr_in seraddr;
memset(&seraddr, 0, sizeof(seraddr));
seraddr.sin_family = AF_INET; //ipv4
seraddr.sin_port = htons(8888); //设置端口
seraddr.sin_addr.s_addr = inet_addr("123.123.12.123"); // inet_addr把ip点分十进制转为二进制形式. ip地址要是本机能存在的.
if(bind(sockfd, (struct sockaddr*)&seraddr, sizeof(seraddr)) == -1)
{
err_log("bind");
}
printf("bind----\n");
/*监听*/
if(listen(sockfd, 5) == -1)
{
err_log("listen");
}
printf("listen----\n");
/*接收请求*/
int connfd = accept(sockfd, NULL, NULL);
if(connfd == -1)
{
err_log("accept");
}
printf("accept-----\n");
/*数据收发*/
char buf[32] = {0};
recv(connfd, buf, sizeof(buf),0); //read(connfd, buf, sizeof(buf));
printf("recv=%s\n", buf);
send(connfd, "ok", 3, 0); //write(connfd, "sb", 3);
/*关闭套接字*/
close(connfd);
close(sockfd);
return 0;
}
PS:
端口:系统占用 0~1023 用户一般使用 1024 ~ 65535
字节序转换:
htons(): 主机字节序转网络字节序(以short类型转换)
ntohs(): 网络字节序转主机字节序(以short类型转换)
htonl(): 主机字节序转网络字节序(以long类型转换)
ntohl(): 网络字节序转主机字节序(以long类型转换)
ip地址转换函数:
in_addr_t inet_addr(const char *cp); 点分十进制转为地址二进制形式
char *inet_ntoa(struct in_addr in); 地址二进制形式转为点分十进制.
五、TCP客户端编写流程
{
1.创建套接字--socket()
2.设置服务器地址信息 struct sockaddr_in.
3.主动请求连接服务器 connect().
4.数据的收发. recv()/send()
5.关闭套接字 close().
}
ping: 测试主机网络是否连通. 用法: ping + ip
ifconfig: 查看当前主机的网络信息.
netstat -na: 查看系统网络连接状态. 通常需要与 grep一起使用
netstat -na | grep "port或ip"
客户端示例代码:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#define PORT 8888
#define err_log(log) do{perror(log);exit(-1);}while(0)
int main(int argc, char *argv[])
{
if(argc != 3)
{
fprintf(stderr, "Usage:%s <ip> <port>\n", argv[0]);
return -1;
}
/*1.创建套接字*/
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd == -1)
{
err_log("socket");
}
printf("socket-----success\n");
/*2.设置服务器地址信息*/
struct sockaddr_in seraddr;
memset(&seraddr, 0, sizeof(seraddr));
seraddr.sin_family = AF_INET; //ipv4
seraddr.sin_port = htons(atoi(argv[2])); //设置端口
//seraddr.sin_addr.s_addr = inet_addr("0.0.0.0"); // inet_addr把ip点分十进制转为二进制形式. ip地址要是本机能存在的.
seraddr.sin_addr.s_addr = inet_addr(argv[1]);
/*3.请求连接*/
if(connect(sockfd, (struct sockaddr*)&seraddr, sizeof(seraddr)) == -1)
{
err_log("connect");
}
printf("connect success\n");
char buf[32] = {0};
int ret = 0;
while(1)
{
/*4.数据收发*/
memset(buf, 0, sizeof(buf));
printf("send msg:");
fgets(buf, sizeof(buf), stdin);
buf[strlen(buf)-1] = '\0';
if(strcmp(buf, "quit") == 0)
{
printf("退出连接--------\n");
break;
}
ret = send(sockfd, buf, sizeof(buf),0);
if(ret == 0)
{
printf("client close\n");
break;
}
}
/*5.关闭套接字*/
close(sockfd);
return 0;
}
总结
本文的系统环境为Ubuntu,以上编程传输仅限于局域网的不同机器或者同一机器。按以上的流程就能写简单的TCP服务器,也可以自己动手试试写简单的文件服务器。可以在这基础上加更多东西,linux下一切皆文件,只要你设置好带宽,处理好数据收发,就应该什么都能传。
暂时先写简单的TCP客户端和服务器,后续文章有UDP协议的服务器和客户端,以及并发服务器;