一、之前的单机IPC回顾
管道--------------------->父子间通信
消息队列----------------->内核里经营一系列消息队列
共享内存----------------->内核创建一个共享内存
信号--------------------->A通过B的PID进程标识符给它发消息
信号量------------------->对临界资源,共享内存做PV操作
(特点:依赖于内核,造就功能缺陷,无法多机通信)
网络IPC(多机)
地址----->1.IP地址
2。端口号(交互作用,个人理解为管家接待客人)
协议(数据格式):HTTP TCP UDP
---1.TCP:面向连接(A,B先建立网络连接,好比A向B打电
--- 话,B接听,比较可靠)
socket(套接字)------
---2.UDP:面向报文(好比A向B发送短信,B看到没看到不
--- 知道,不可靠)
TCP/UDP对比
1.TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不
需要建立连接。
2.TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重
复且按序到达;UDP尽最大努力交付,即不保证可靠交付
3.TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的,
UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如
IP电话,实时视频会议等)
4.每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互
通信。
5.TCP首部开销20字节;UDP首部开销小,只有8个字节
6.TCP的裸机通信信道是全双工的可靠信道,UDP则是不可靠信道
端口号作用:
1.一台拥有TP地址的主机可以提供许多服务,比如Web服务,FTP服务,SMTP服务等
2.这些服务完全通过1个IP地址来实现。那么,主机是怎么区分不同的网络服务呢?
显然不能只靠IP地址,因为IP地址与网络服务的关系是一对多的关系
3.实际上是通过“IP地址+端口号”来区分不同的服务的
端口提供了一种访问通道
服务器一般是通过知名端口号来识别的。例如,对于每个TCP/IP实现来说,FTP服务
器的TCP端口号都是21,每个Telnet服务器的TCP端口号都是23,每个TFTP(简单文
件传送协议)服务器的UDP端口号都是69
一般自己写,选择5000到10000之间
二、字节序(网络编程字节序很重要)
字节序是指多字节数据在计算机内存中存储或网络传输时各字节的存储顺序。
常见序:1.Little endian:小端字节序(将低序字节存储在起始地址)
2.Big endian:大端字节序(将高序字节存储在起始地址)
网络字节序 = 大端字节序
x86系列CPU都是小端字节序
例:在内存中双子0x01020304的存储方式(一个字节8位,0代表16进制,需4个二进制表示)
内存地存 4000 & 4001 & 4002 & 4003
LE 04 03 02 01(低序字节放起始地址)
BE 01 02 03 04 (高序字节放起始地址)
例:如果我们把0x1234abcd写入到以0x0000开始的内存中,则结果为
BE LE
0x0000 0x12 0xcd
0x0001 0x34 0xab
0x0002 0xab 0x34
0x0003 0xcd 0x12
三、socket编程
相关API:
1.创建套接字:指定将“汉语”(连接协议)
int socket(int domain,int type,int protacol);
*domain:
指定所使用的协议族,通常为AF_INET,表示互联网协议族(TCP/IP协议族):
AF_INET IPv4-----------因特网域
AF_INET6 IPv6----------因特网域
AF_UNIX Unix-----------域
AF_ROUTE---------------路由套接字
AF_KEY-----------------密钥套接字
AF_UNSPEC--------------未指定
*type参数指定socket的类型:
SOCK_STREAM:流式套接字提供可靠的,面向连接的通信流;它使用TCP协议,
从而保证了数据传输的正确性和顺序性
SOCK_DGRAM:数据报套接字定义了一种无连接的服,数据通过相互独立的报文
进行传输,是无序的,并且不保证是可靠,无差错的。它使用数
据报协议UDP
SOCK_RAW:允许程序使用低层协议,原始套接字对低层协议如IP或ICMP进行直
接访问,功能强大但使用较为不便,主要用于一些协议的开发。
*protacol:
通常赋值0
0选择type类型对应的默认协议
IPPROTO_TCP -----------TCP传输协议
IPPROTO_UDP -----------UDP传输协议
IPPROTO_SCTP ----------SCTP传输协议
IPPROTO_TIPC ----------TIPC传输协议
2.bind()函数:IP号端口号与相应描述字赋值函数(添加或绑定IP地址和端口号到socketfd)
int bind(int sockfd,const struct sockaddr *addr,socklen_t addrlen)
*sockfd:是一个socket描述符
*addr:是一个指向包含本机IP地址及端口号等信息的sockaddr类型的指针,值向要绑定
给sockfd的协议地址结构。‘’
*addrlen:第二个参数的长度
IPv4对应的是:
struct sockaddr_in {
__kernel_sa_family_t sin_family;------协议族
__be16 sin_port;--------端口号
struct in_addr sin_addr;--------IP地址结构体
unsigned char sin_zero[8];-----填充,无意义,只为结构体在
内存中对齐
};
struct sockaddr_in 查找:
$:cd /usr/include/
$:grep "struct sockaddr_in {" * -nir
:linux/in.h:232:struct sockaddr_in {
$:vi linux/in.h + 232
地址转换API
int inet_aton(const char* straddr,struct in_addr *addrp)
把字符串形式的“192.168.1.123”转为网络能识别的格式
char* inet_ntoa(struct in_addr inaddr);
把网络格式的ip地址转为字符串形式
3.listen()函数:监听设置函数
int listen(int sockfd,int backlog);
设置能处理的最大连接数,listen()并未开始接受连线,只是设置sockect的listen
模式,listen函数只用于服务器端,服务器进程不知道要与谁连接,因此,他不会主动地
要求与某个进程连接,只是一直监听是否有其他客户进程与之连接,然后响应该连接请求,
并对它做出处理,一个服务进程可以同时处理多个客户进程的连接。主要就两个功能:将
一个未连接的套接字转换为一个被动套接字(监听),规定内核为相应套接字排队的最大连
接数。
内核为任何一个给定监听套接字维护两个队列:
未完成连接队列,每个这样的SYN报文段对应其中一项;已由某个客户端发出并到
达服务器,而服务器正在等待完成相应的TCP三次握手过程。这些套接字处于SYN_R
EVD状态;
已完成连接队列,每个已完成TCP三次握手过程的客户端对应其中一项,这些套接
字处于ESTABLISHED状态;
*sockfd:sockfd是socket系统调用返回的服务器端socket描述符
*backlog:指定在请求队列中允许的最大请求数
4.accept()函数-------连接
int accept(int sockfd,struct sockaddr *addr,socklent *addrlen)
accpet函数由TCP服务器调用,用于从已完成连接队列队头返回下一个已完成连接。
如果已完成连接队列为空,那么进程被投入睡眠。
*sockfd:sockfd是socket系统调用返回的服务器socket描述符。
*addr:用来返回已连接的对端(客户端)的协议地址
*addrled:客户端地址长度
返回值:该函数的返回值是一个新的套接字描述符,返回值是表示已连接的套接字描述符,
而第一个参数是服务器监听套接字描述符。一个服务器通常仅仅创建一个监听套接字,它
在该服务器的生命周期内一直存在。内核为每个由服务器进程接收的客户连接创建一个已
连接套接字(表示TCP三次握手已完成),当服务器完成对某个给定客户的服务时,相应的
已连接套接字就会被关闭。
数据收发可以用read,write;也可以用send,recv、和read,write用法一样,
不过第四个参数写0,做备份
5.connect()函数:客户机连接主机
int connect(int sockfd,const sockaddr *addr,socklen_t addrlen);
该函数用于绑定之后的client端(客户端),与服务器建立连接。
*sockfd:是目的服务器的sockect,描述符
*addr:是服务器端的IP地址和端口号的地址结构体指针
*addrlen:地址长度常被设置为sizeof(struct sockaddr)
返回值:成功返回0,失败返回-1并在errno中包含相应的错误码
6.字节序转换API-----与#include<linux/in.h>冲突
uint16_t htons(uint16_t host16bitvalue);--------返回网络字节序的值(短)
uint32_t htonl(uint32_t host32bitvalue);--------返回网络字节序的值(长)
uint16_t ntohs(uint16_t net16bitvalue);---------返回主机字节序的值uint32_t
ntohl(uint32_t net16bitvalue);------------------返回主机字节序的值
h---host
n---net
s---short(两个字节)
l---long(四个字节)
socket服务器代码实现一
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
//#include <linux/in.h>
#include <arpa/inet.h>
int main()
{
int s_fd;
s_fd = socket(AF_INET,SOCK_STREAM,0);
if(s_fd == -1){
perror("socket:");
exit(-1);
}
struct sockaddr_in s_addr;
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(8989);
inet_aton("192.168.43.115",&s_addr.sin_addr);
bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
listen(s_fd,10);
int c_fd = accept(s_fd,NULL,NULL);
printf("connect\n");
while(1);
return 0;
}
在windows命令终端:ping 192.168.43.115--------ping通后
:telnet 192.168.43.115 8989-------实现连接
也可在虚拟机另一终端测试
socket服务器代码实现二
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
//#include <linux/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
int main()
{
int s_fd;
int n_read;
char readbuf[128] = {0};
char *msg = "welcome to Beijing";
s_fd = socket(AF_INET,SOCK_STREAM,0);
if(s_fd == -1){
perror("socket:");
exit(-1);
}
struct sockaddr_in s_addr;
struct sockaddr_in c_addr;
memset(&s_addr,0,sizeof(struct sockaddr_in));
memset(&c_addr,0,sizeof(struct sockaddr_in));
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(8989);
inet_aton("192.168.43.115",&s_addr.sin_addr);
bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
listen(s_fd,10);
int len = sizeof(struct sockaddr_in);
int c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&len);
if(c_fd == -1){
perror("accept:");
}
n_read = read(c_fd,readbuf,128);
if(n_read == -1){
perror("read:");
}else{
printf("get message:%d,%s\n",n_read,readbuf);
}
write(c_fd,msg,strlen(msg));
return 0;
}
服务器运行,客户端发消息,服务器读到打印出来并向客户端返回消息welcome to Beijing
在虚拟机终端或windows下都可以按上述方法测试验证
socket客户端代码实现
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
int main()
{
int c_fd;
char readBuf[128];
char *msg = "lpl";
struct sockaddr_in c_addr;
memset(&c_addr,0,sizeof(struct sockaddr_in));
// 1.socket
c_fd = socket(AF_INET ,SOCK_STREAM,0);
if(c_fd == -1){
perror("socket");
exit(-1);
}
c_addr.sin_family = AF_INET;
c_addr.sin_port = htons(8989);
inet_aton("192.168.43.115",&c_addr.sin_addr);
// 2.connect
if(connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr)) == -1){
perror("connect");
exit(-1);
}
// 3.send
write(c_fd,msg,strlen(msg));
// 4.read
int n_read = read(c_fd,readBuf,128);
if(n_read == -1){
perror("read");
}else{
printf("get message from server:%d,%s\n",n_read,readBuf);
}
return 0;
}
与上面服务器二,一起实现一组信息传递
实现双方聊天
server.c
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
//#include <linux/in.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include<stdlib.h>
#include<string.h>
#include <unistd.h>
int main(int argc,char **argv)
{
int s_fd;
int c_fd;
int n_read;
char readBuf[128];
char msg[128] ={0};
struct sockaddr_in s_addr;
struct sockaddr_in c_addr;
if(argc != 3){
printf("param is not good\n");
exit(-1);
}
memset(&s_addr,0,sizeof(struct sockaddr_in));
memset(&c_addr,0,sizeof(struct sockaddr_in));
// 1.socket
s_fd = socket(AF_INET ,SOCK_STREAM,0);
if(s_fd == -1){
perror("socket");
exit(-1);
}
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(atoi(argv[2]));
// s_addr.sin_addr.s_addr = inet_aton("");
inet_aton(argv[1],&s_addr.sin_addr);
// 2.bind
bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
// 3.listen
listen(s_fd,10);
// 4.accept
int clen = sizeof(struct sockaddr_in);
while(1){
c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&clen);
if(c_fd == -1){
perror("accept");
}
printf("get conect:%s\n",inet_ntoa(c_addr.sin_addr));
if(fork() == 0){
if(fork() == 0){
while(1){
memset(msg,0,sizeof(msg));
printf("input: ");
gets(msg);
write(c_fd,msg,strlen(msg));
}
}
// 5.read
while(1){
memset(readBuf,0,sizeof(readBuf));
n_read = read(c_fd,readBuf,128);
if(n_read == -1){
perror("read");
}else{
printf("get message;%d,%s\n",n_read,readBuf);
}
}
break;
}
}
return 0;
}
client.c
#include<stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
//#include <linux/in.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include<stdlib.h>
#include<string.h>
#include <unistd.h>
int main(int argc,char **argv)
{
int c_fd;
int n_read;
char readBuf[128];
// char *msg = "lpl";
char msg[128] = {0};
struct sockaddr_in c_addr;
if(argc != 3){
printf("param is not good\n");
exit(-1);
}
memset(&c_addr,0,sizeof(struct sockaddr_in));
// 1.socket
c_fd = socket(AF_INET ,SOCK_STREAM,0);
if(c_fd == -1){
perror("socket");
exit(-1);
}
c_addr.sin_family = AF_INET;
c_addr.sin_port = htons(atoi(argv[2]));
// s_addr.sin_addr.s_addr = inet_aton("");
inet_aton(argv[1],&c_addr.sin_addr);
// 2.connect
if(connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr)) == -1){
perror("connect");
exit(-1);
}
while(1){
// 3.send
if(fork() == 0){
while(1){
memset(msg,0,sizeof(msg));
printf("input: ");
gets(msg);
write(c_fd,msg,strlen(msg));
}
}
// 4.read
while(1){
memset(readBuf,0,sizeof(readBuf));
n_read = read(c_fd,readBuf,128);
if(n_read == -1){
perror("read");
}else{
printf("get message from server:%d,%s\n",n_read,readBuf);
}
}
}
return 0;
}
./server 192.168.43.100 8182
./client 192.168.43.100 8182
实现双方聊天
|