一、为什么要使用网络编程
五种进程间通信的方式都是基于linux内核的单机通信,无法完成多机之间的通信,而网络编程,socket,可以实现多机之间的通信,很容易完成服务器与客户端之间的通信。
二、TCP协议与UDP协议
TCP是面向连接的;UDP是无连接的,发送数据之前不需要建立连接。
TCP提供可靠服务,即通过TCP建立连接发送的数据,无差错,无丢失,无重复,按序传送;UDP则是尽最大努力交付,不可靠传输。
TCP是面向字节流;UDP是面向报文的,UDP没有拥塞控制,即网络面对拥塞不会使源主机发送速率降低。
TCP连接是点对点的;UDP支持一对一、一对多、多对一和多对多交互通信。
TCP首部开销大,20个字节;UDP首部开销小,8个字节。
TCP的逻辑通信信道是全双工的可靠信道;UDP是不可靠信道。
三、端口号的作用
一台PC机只有一个IP地址,但可以完成许多服务,web服务、ftp服务等,主机显然不能通过一个ip地址区分这么多服务,而端口提供了一种访问通道,通过ip地址和端口号,便可以精确访问某个服务。
四、字节序
概念:字节序是指多字节数据在计算机内存中存储,或者网络传输时各字节的存储顺序。
Little endian 小端字节序:是将低序的字节存储在起始位置
Big endian 大端字节序:是将高序的字节存储在起始位置
网络字节序=大端字节序
举个例子 ,理解一下
内存中有0x01020304的存储方式
其中01为高序字节,04为低序字节
内存地址有4000&4001&4002&4003
大端字节序是指,01高序字节存储在4000的内存中,其余依次排列
小端字节序是指,04低序字节存储在4000的内存中,其余依次排列
五、网络编程API概要
1、函数原型
服务器相关api
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
//1、创建套接字,成功返回套接字描述符,失败返回-1
int socket(int domain, int type, int protocol);
参数一:使用的协议族 通常有 AF_INET(IPv4) AF_INET6(IPv6)两个宏
参数二:使用的协议类型 通常有 SOCK_STREAM(TCP协议) SOCK_DGRAM(UDP协议)两个宏
参数三:通常赋值为0,选择与type类型相对应的协议
//2、绑定ip地址和相对应的端口,成功返回0,失败返回-1
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
参数一:socket返回的套接字描述符
参数二:是一个结构体配置ip地址相关信息
参数三:结构体的大小
参数二的结构体 通常用另一个结构体替代,需要做结构体类型的强制转换
struct sockaddr_in {
__kernel_sa_family_t sin_family; /* 协议族 */
__be16 sin_port; /* 端口号 */
struct in_addr sin_addr; /* IP地址结构体 */
unsigned char sin_zero[8] /*填充 没有实际意义*/
};
struct in_addr {
__be32 s_addr;
};
//3、监听是否有客户端访问
int listen(int sockfd, int backlog);
参数二:监听的个数
//4、用来接受已连接的客户端,成功返回一个通道id用于后续 write read操作,失败返回-1
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数二:用来返回已连接客户端的IP地址
参数三:客户端IP地址的大小
//IP地址转换的api
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int inet_aton(const char *cp, struct in_addr *inp);
将字符串“192.168.1.1”形式转换为网络识别的格式
char *inet_ntoa(struct in_addr in);
将网络格式的IP地址转换为字符串形式
//字节序转化api
#include <arpa/inet.h>
uint16_t htons(uint16_t hostshort)//返回网络字节序的值
uint32_t htonl(uint32_t hostlong);//返回网络字节序的值
uint32_t ntohl(uint32_t netlong);//返回主机字节序的值
uint16_t ntohs(uint16_t netshort);//返回主机字节序的值
//5、数据收发
read write &recv send 一般用于TCP
recvmsg sendmsg &recvfrom sendto 一般用于UDP
客户端相关api
//1、创建套接字,成功返回套接字描述符,失败返回-1
int socket(int domain, int type, int protocol);
//2、连接服务器
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
参数二:服务器的IP地址和端口对应的结构体指针
参数三:地址长度,结构体大小
服务器demo
//sever.c
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
int main()
{
int sockid;
int s_id;
char readBuf[256];
struct sockaddr_in saddr;//服务器IP地址结构体
struct sockaddr_in caddr;//客户端IP地址结构体
saddr.sin_family = AF_INET;
saddr.sin_port = htons(8888);//配置服务器端口
inet_aton("192.168.1.101",&saddr.sin_addr);//服务器地址
sockid=socket(AF_INET ,SOCK_STREAM,0);
if(sockid==-1)
{
printf("socket error\n");
exit(-1);
}
bind(sockid,(struct sockaddr *)&saddr,sizeof(struct sockaddr_in));
listen(sockid,8);//监听socket
int addrlen =sizeof(struct sockaddr_in);
while(1)//不断的接受客户端的连接
{
s_id=accept(sockid,(struct sockaddr *)&caddr,&addrlen);
//接受客户端socket返回通道id
printf("client ip addr is %s\n",inet_ntoa(caddr.sin_addr));
write(s_id,"hello client",15);//往客户端写数据
read(s_id,readBuf,sizeof(readBuf));//从客户端读数据
printf("from client:%s\n",readBuf);
}
return 0;
}
客户端demo
//client.c
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <netinet/in.h>
int main()
{
int sockid;
struct sockaddr_in saddr;//连接的服务器的IP地址结构体
char readBuf[128];
saddr.sin_family = AF_INET;
saddr.sin_port = htons(8888);
inet_aton("192.168.1.101",&saddr.sin_addr);
sockid = socket(AF_INET,SOCK_STREAM ,0);
if(sockid == -1)
{
printf("socket error\n");
exit(-1);
}
connect(sockid,(struct sockaddr *)&saddr,sizeof(struct sockaddr_in));//连接服务器
read(sockid,readBuf,sizeof(readBuf));//读服务器的数据
write(sockid,"i am client",128);//给服务器发送数据
printf("receive context %s\n",readBuf);
return 0;
}
执行结果
sever:
client ip addr is 192.168.1.101
from client:i am client
client:
receive context hello client