网络编程概述:
1、为什么需要网络编程?
a、使用场景需求普遍是,视频、电话、信息等,都是建立于单机与单机之间的通讯。
b、网络通讯可以建立在多台单机连接通信,不局限于单机的内核通讯(如进程间通讯、管道、消息队列、共享内存等都依赖于内核)。
2、建立网络编程
地址IP+端口号
3、网络信息交互
协议(http、tcp、DUP);什么是协议呢,简单的比方,像串口通讯都约定的8位数据位,1位停止位等。
socket套接字网络编程
1、普遍使用TCP/UDP这两种通讯协议。
TCP/UDP协议的对比:
a、TCP面向连接(如打电话要先拨号建立连接);UDP是无连接,即发送数据之前,不需建立连接。
b、TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按顺序到达;UDP尽最大努力交付,即不保证可靠交付。
c、TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向保文的UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速度减低(对实时应用很有用,如IP电话,实时视频会议等)
d、每一条TCP连接只能点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信。
e、TCP首部开销20字节;UDP的首部开销小,只有8字节。
h、TCP的逻辑通信信道是半双工的可靠通信道,UDP则是不可靠信道。
2、字节序:
概述:
字节序是指多字节数据在计算机内存中存储或者网络传输时各字的存储顺序
常见序:
Little endian 小端字节序,将低字节存储在起始位地址
Big endian 大段字节序 将高字节存储在起始位地址
网络字节序 = 大端字节序
字节序转换API:
#include <netinet/in.h>
uint16_t htons(uin16_ t host16bitvalue);
//返回网络字节序的值
uint32_t htonl(uint32_ t host32bitvalue);
//返回网络字节序的值
uint16_t ntons(uint16_ t net16bitvalue);
//返回主机字节序的值uint32_t
ntons(uint32_ t net32bitvalue);
//返回主机字节序的值
h代表host,n代表net,s代表short(两个字节),l代表long(4个字节序)通过上面4个函数可以实现主机字节序和网络字节序之间的转换。有是可以用INDDR_ANY,INADDR_ANY指定的地址让操作系统自己获取。
3、TCP服务器搭建的步骤:
1、创建套接字//sockt()
2、为套接字添加信息(IP地址和端口号)//bind()
3、监听网络连接 //listen()
4、监听到有客户端接入,接受一个连接 //accept()
5、数据交互 //read()、write()
6、关闭套接字,断开连接 //close()
4、 Socket()函数创建套接字
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
参数
domain参数1:
指明所使用的协议族,通用为AF_INET ,表示互联网协议族(TCP/IP协议族)
1、AF_INET IPv4 因特网域
2、AF_INET IPv4 因特网域
3、AF_INET Unix 域
4、AF_ROUTE 路由套接字
5、AF_KEY 密钥套接字
6、AF_UNSPEC 未指定
Type参数2
指定socket的类型:
1、SOCK_STREAM:
流式套接字提供可靠的,面向连接的通信流,它从而保证数据传输正确性和顺序性。
2、SOCK_DGRAM:
数据报套接字定义一种无连接服,数据通过相互独立的文进制传输,是无序,并且不保证可靠、无差错,它使用数据协议UDP
3、SOCK_RAW :
允许程序用底层协议,原始套接字允许对底层协议IP或者ICMP进行直接访问,功能强大但使用较为不便,主要用于一些协议的开发
Protocol参数:
通常赋值“0”。
1、0选择type类型应对的默认协议
2、IPPROTO_TCP TCP 传输协议
3、IPPROTO_UDP UDP 传输协议
4、IPPROTO_SCTP SCTP 传输协议
5、IPPROTO_TIPC TIPC 传输协议
5、bind()函数:IP端口号与相应描述字赋值函数
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd参数: socket套接字的描述符
addr参数: 是一个指向有本机IP地址及端口号等信息的socketadder类型指针,指向要绑定sockfd的协议地址结构根据地址创建socket时的地址协议族的不同而不同
Addrlen参数: 参数二字节的长度
addr结构体参数配置如下:
//ipv4 对应的变量
搜索结构体定义的地方命令行查
1、cd /usr/include/
2、grep “struct sockaddr_in {” * -nir // *在当前目录底下,r递归找,n找到显示行号,i不区分大小写找,
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
}
该为同等转发(通常用):
Struct sockaddr_in{
Sa_family sin_family; //协议族
In_port_t sin_port; //端口号
Struct in_adder sin_addr; //IP地址结构体
Unsigned char sin_zero[8];
/*填充没有实际意义只是为限sockadder结构体在内存中对齐这样两者才能相互转换*/
}
地址转行API:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int inet_aton(const char *stradder, struct in_addr *adderip);
//把字符串形式” 192.168.1.123” 转换为网络能识别的格式
char *inet_ntoa(struct in_addr inadder);
//把网络格式的IP地址转为字符串形式
6、listen()函数:监听设置函数
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int listen(int sockfd, int backlog);
功能
1、设置能处理最大的联机数,listen()并未开始接受连接,只是设置socket的listen函数只能用于服务器端,服务器进程不知道要与谁连接,因此,它不会主动要求与某个进程,只是一直监听是否有其他客户端进程与之连接,然后响应该连接请求,并对他做出处理,一个服务器进程可以同时处理多个客户进程连接,主要两个功能:将一个未连接的套接字转换为一个被动套接字(监听),规定内核为相应套接字队列的最大连接数。
2、内核为任何一个监听套接字维护两个对列:
@未完成连接对列,每个这样的SYN报文段对应其中一项,已由某个客户端发出并到达服务器,而服务器正在等待完成相应的TCP三次握手过程,这些套接字处于SYN_REVD状态。
@已完成连接对列,每个已完成TCP三次握手过程的客户端对应一项,这些套接字处于ESTABLISHED状态。
参数:
Sockfd:
Sockfd是socket系统返回服务器的描述符。
backlog:
backlog指定在请求对列中允许的最大请求参数
7、accept()函数 连接客户端
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能:accept函数有TCP服务器调用,用于从已完成连接对列对头返回下一个已完成连接,如果以完成连接对列为空,那么进程被投入睡眠。
参数:
sockfd: Sockfd是socket系统返回服务器的描述符
addr: 用来返回已连接对接端(客户端)的协议地址
addrlen: 客户端地址长度
返回值:
返回值是一个新的套接字描述符,返回值是表示已经连接的套接字描述符,而第一个参数是服务器监听套接字描述符,一个服务器通常仅仅创建一个监听套接字,它在该服务器的生命周期内一直存在,内核为每个由服务器进程接受的客户端连接创建一个套接字(表TCP三次握手已完成)当服务器完成对某个给定客户的服务时,相应的已连接套接字就被关闭。
8、read()、write() 数据的收发
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
//接收数据
ssize_t write(int fd, const void *buf, size_t count);
//发送数据
9、数据收发常用函数第二套API
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
//发送函数
//包含3要素:套接字描述符,待发数据buf,数据长度len,
//函数只对处于连接状态的套接字使用,参数
//flags阻塞
ssize_t recv(int sockfd, void *buf, size_t len, int flags); // 接收函数
//包含3要素:套接字描述符,待发数据buf,数据长度len,
//函数只对处于连接状态的套接字使用,参数
//flags阻塞
服务端demo:
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc,char **argv)
{
int c_fd;
int s_fd;
char readbuf[128];
char writebuf[128];
struct sockaddr_in s_adder;
struct sockaddr_in c_adder;
int mark =0;
if(argc != 3){
printf("get can shu wu\n");
exit(-1);
}
//socket int socket(int domain, int type, int protocol);
s_fd = socket(AF_INET,SOCK_STREAM,0);
if(s_fd == -1){
perror("socket");
exit(-1);
}
//bind int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
//int inet_aton(const char *cp, struct in_addr *inp);
// struct sockaddr_in adder;
s_adder.sin_family = AF_INET; //协议族
s_adder.sin_port = htons(atoi(argv[2])); ///端口号
inet_aton(argv[1],&s_adder.sin_addr );//IP地址结构体
int b_fd = bind(s_fd,(struct sockaddr *)&s_adder,sizeof(struct sockaddr_in));
if(b_fd == -1){
perror("bind");
}
//listen
listen(s_fd,10);
//accept int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
int len = sizeof(struct sockaddr_in);
while(1){
c_fd = accept(s_fd,(struct sockaddr *)&c_adder,&len);
printf("connet suess\nclinet_ip:%s\n",inet_ntoa(c_adder.sin_addr));
// ssize_t read(int fd, void *buf, size_t count);
mark++;
if(fork() == 0){
if(fork() == 0){
while(1){
memset(writebuf,'\0',128);
gets(writebuf);
write(c_fd,writebuf,sizeof(writebuf));
sleep(2);
}
}
while(1){
memset(readbuf,'\0',128);
read(c_fd,readbuf,sizeof(readbuf));
printf("\nget%d:%s\n",mark,readbuf);
}
}
}
system("pause");
return 0;
}
客户端demo:
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc,char **argv)
{
int c_fd;
char writebuf[128];
har readbuf[128];
//struct sockaddr_in s_adder;
struct sockaddr_in c_adder;
//socket int socket(int domain, int type, int protocol);
if(argc != 3){
printf("can shu chu cuo\n");
exit(-1);
}
c_fd = socket(AF_INET,SOCK_STREAM,0);
if(c_fd == -1){
perror("socket");
exit(-1);
}
//int inet_aton(const char *cp, struct in_addr *inp);
// struct sockaddr_in adder;
c_adder.sin_family = AF_INET; //协议族
c_adder.sin_port = htons(atoi(argv[2])); ///端口号
inet_aton( argv[1],&c_adder.sin_addr );//IP地址结构体
if( connect(c_fd,(struct sockaddr *)&c_adder,sizeof(struct sockaddr_in)) == -1 ){
perror("clinet");
exit(-1);
}
while(1){
if(fork() == 0){
while(1){
memset(readbuf,0,128);
read(c_fd,readbuf,sizeof(readbuf));
printf("\nget:%s\n",readbuf);
sleep(2);
}
}
while(1){
memset(writebuf,'\0',128);
printf("put:");
gets(writebuf);
write(c_fd,writebuf,strlen(writebuf));
}
}
system("pause");
return 0;
}
下面实现服务端与客户端信息交互:
服务端显示:
CLC@Embed_Learn:~$
CLC@Embed_Learn:~$
CLC@Embed_Learn:~$ vi server.c
CLC@Embed_Learn:~$
CLC@Embed_Learn:~$ gcc server.c -o server
CLC@Embed_Learn:~$ ./server 192.168.101.100 8888
connet suess
clinet_ip:192.168.101.100
holle
get1:hille too
my name is cheng xu yuan
get1:meet too
客户端显示:
CLC@Embed_Learn:~$
CLC@Embed_Learn:~$
CLC@Embed_Learn:~$ vi clinet.c
CLC@Embed_Learn:~$
CLC@Embed_Learn:~$ ./clinet1.0 192.168.101.100 8888
put:
get:holle
hille too
put:
get:my name is cheng xu yuan
meet too