参考文章:Linux的SOCKET编程详解_hguisu的博客-CSDN博客_socket编程
一. 网络中进程之间如何通信
UNIX BSD有:管道(pipe)、命名管道(named pipe)软中断信号(signal)
UNIX system V有:消息(message)、共享存储区(shared memory)和信号量(semaphore)等,他们都仅限于用在本机进程之间通信。依赖于内核,无法进行多机通信
端口号作用
- 一台拥有IP地址的主机可以提供许多服务,比如Web服务、FTP服务、SMTP服务等
- 这些服务完全可以通过1个IP地址来实现。那么,主机是怎样区分不同的网络服务呢?显然不能只靠IP地址,因为IP 地址与网络服务的关系是一对多的关系。
- 实际上是通过“IP地址+端口号”来区 分不同的服务的。 端口提供了一种访问通道
- 服务器一般都是通过知名端口号来识别的。例如,对于每个TCP/IP实现来说,FTP服务器的TCP端口号都是21,每个Telnet服务器的TCP端口号都是23,每个TFTP(简单文件传送协议)服务器的UDP端口号都是69。
地址(找到设备):IP地址+端口号(确定要连接的服务)
数据交互:协议(数据格式)--HTTP/TCP/UDP
二. 什么是TCP/IP、UDP
TCP/UDP对比(面试:背下PPT那段话)
1. TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前 不需 要建立连接
2. TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付
3. TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的 UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)
4. 每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信
5. TCP首部开销20字节;UDP的首部开销小,只有8个字节
6. TCP的逻辑通信信道是全双工的可靠信道,UDP则是不靠信道
TCP:面向连接(可靠)
UDP:面向拨问(不可靠)
面试:两种服务对比
字节序?
双字:32位
单字(一个字):8位(8个字节)
电脑的地址32位
三、网络字节序与系统中使用的字节序不同,需要通过API转换
小端字节序:低序字节存放在起始地址(低位地址)
大端字节序:高序字节存放在起始地址(低位地址)
网络字节序=大端字节序
字节序转换API
- uint16_t htons(uint16_t host16bitvalue); //返回网络字节序的值uint32_t htonl(uint32_t host32bitvalue); //返回网络字节序的值
- uint16_t ntohs(uint16_t net16bitvalue); //返回主机字节序的值
- uint32_t ntohl(uint32_t net32bitvalue); //返回主机字节序的值
- h代表host,n代表net,s代表short(两个字节),l代表long(4个字节),通过上面的4个函数可以实现主机字节序和网络字节序之间的转换。有时可以用INADDR_ANY,INADDR_ANY指定地址让操作系统自己获取
四、关于socket
socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。Socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭).
说白了Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
注意:其实socket也没有层的概念,它只是一个facade设计模式的应用,让编程变的更简单。是一个软件抽象层。在网络编程中,我们大量用的都是通过socket实现的。
socket产生多个网络连接通道,用于网络通信,每个通道有IP+端口到作为身份标志,可用操作文件的方式write/read通道中的数据,达成通络通信的效果
五、Socket 服务端和客户端的开发步骤
1.创建套接字
- int socket(int domain, int type, int protocol);//获取套接字(一个服务器的接口)的描述符
2.为套接字添加信息(IP地址和端口号)
- int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- struct sockaddr_in s_addr ; //使用前memset(&s_addr,0,sizeof(struct sockaddr_in))
- 注意:端口号转化成网络可识别 uint16_t htons(uint16_t host16bitvalue); //返回网络字节序的值
IP地址转化成网络可识别
grep "struct sockaddr_in {" * -nir 查找包含此内容的文件
地址转换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()、connect()函数
- int listen(int sockfd, int backlog);//服务端
- int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);//客户端
4.监听到有客户端接入,接受一个连接
- int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); //返回连接connect_fd//当没有获取到三次握手的端口时阻塞在此等待
- //并返回客户端的信息(指针)
注意:其参数与bind相似,第2参数需强制转换成struct sockaddr *类型
5.数据交互
- read()/write()
- recv()/send()
- readv()/writev()
- recvmsg()/sendmsg()
- recvfrom()/sendto()
- 推荐使用recvmsg()/sendmsg()函数,这两个函数是最通用的I/O函数,实际上可以把上面的其它函数都替换成这两个函数
6.关闭套接字,断开连接
int close(int fd);
单次连接例程:
//服务端
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
//#include <linux/in.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
//chenlichen
int main(int argc, char **argv)
{
int s_fd;
int c_fd;
int n_read;
char readbuf[128];
int clen = sizeof(struct sockaddr_in);
int pid;
char *msg = "server get";
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));
//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])); //char->int->net_int
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
while(1){
c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&clen);
if(c_fd == -1){
perror("accept");
}
printf("--get a new conect--\naddr= %s\n",inet_ntoa(c_addr.sin_addr));
pid = fork();
if(pid == 0){
//5.read
n_read = read(c_fd,readbuf,128);
if(n_read == -1){
perror("read");
}else{
printf("receive:%d(bytes) %s\n\n",n_read,readbuf);
}
//6.write
write(c_fd,msg,strlen(msg));
break;
}
}
return 0;
}
//客户端
#include<stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <errno.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
//int inet_aton(const char *cp, struct in_addr *inp);
int main(int agrc,char **argv)
{
int c_fd;
int n_read;
char readbuf[128];
char msg[128];
int c_addrlen = sizeof(struct sockaddr_in);
struct sockaddr_in c_addr; //client mes
//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])); //char->int->net_int
inet_aton(argv[1],&c_addr.sin_addr);
//2.connect
if(connect(c_fd,(struct sockaddr *)&c_addr,c_addrlen) == -1){
perror("connect");
exit(-1);
}else{
printf("connect success\n");
}
//3.write
printf("input: ");
memset(msg,'\0',sizeof(msg));
scanf("%s",msg);
write(c_fd,msg,strlen(msg));
//4.read
n_read = read(c_fd,readbuf,strlen(readbuf));
printf("%d,%s\n",n_read,readbuf);
return 0;
}
多连接服务端自动回复例程:
//服务端
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
//#include <linux/in.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
//chenlichen
int main(int argc, char **argv)
{
int s_fd;
int c_fd;
int n_read;
char readbuf[128];
int clen = sizeof(struct sockaddr_in);
int pid;
int mark = 0;
char dat[128] = {"nihao"};
char msg[128] = {0};
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));
printf("sizeof(readbuf):%ld\n",sizeof(readbuf));
printf("sizeof(dat):%ld\n",sizeof(dat));
printf("strlen(dat):%ld\n",strlen(dat));
if(argc != 3){
printf("parameters less!\n");
exit(-1);
}
//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])); //char->int->net_int
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
while(1){
c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&clen);
if(c_fd == -1){
perror("accept");
}
mark++;
printf("--get a new conect--\naddr= %s\n",inet_ntoa(c_addr.sin_addr));
if(fork() == 0){
if(fork() == 0){
while(1){
//6.write
memset(msg,0,sizeof(msg));
sprintf(msg,"connect NO.%d",mark);
write(c_fd,msg,sizeof(msg));
sleep(3);
}
}
while(1){
memset(readbuf,0,sizeof(readbuf));
//5.read
n_read = read(c_fd,readbuf,128);
if(n_read == -1){
perror("read");
}else{
printf("receive:%d(bytes) %s\n\n",n_read,readbuf);
}
}
break;
}
}
return 0;
}
windowPC端
//客户端
#include<stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <errno.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
//int inet_aton(const char *cp, struct in_addr *inp);
int main(int agrc,char **argv)
{
int c_fd;
int n_read;
char readbuf[128];
char msg[128];
int c_addrlen = sizeof(struct sockaddr_in);
struct sockaddr_in c_addr; //client mes
//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])); //char->int->net_int
inet_aton(argv[1],&c_addr.sin_addr);
//2.connect
if(connect(c_fd,(struct sockaddr *)&c_addr,c_addrlen) == -1){
perror("connect");
exit(-1);
}else{
printf("connect success\n");
}
//3.write
printf("input: ");
memset(msg,'\0',sizeof(msg));
scanf("%s",msg);
write(c_fd,msg,strlen(msg));
//4.read
n_read = read(c_fd,readbuf,strlen(readbuf));
printf("%d,%s\n",n_read,readbuf);
return 0;
}
调用telnet连接 : telnet socketIP地址 端口号
客户端代码实现
ASCII 简a,系统中的都是ASCII
注意:char *str1 和char str2[128]的区别
str1写入用strcp();str2可以使用gets/scanf,str1不能使用
聊天室 思路:server:1.server端while(1){}一直等待连接,有连接时fork()内进行数据交互
2.数据交互:A、fork()进行写入;B、while(1)一直等待读(fd内无数据时阻塞)写(gets())
C、读写前必须清空缓存(memset())
client:1.client端connect()成功后while(1){}内一直数据交互,A、fork()进行写入;B、while(1)一直等待读(fd内无数据时阻塞)写(gets())
C、读写前必须清空缓存(memset())
存在Bug:当2个client接入时,server发送数据只能到达1个client(资源竞争造成)
多方消息收发server 端自动回复就不会出现此问题
聊天室改进:
引入线程解决问题ftp服务器
注意:127.0.0.1 意思时获取本机的地址
- 对服务端:
1.获取服务器的文件 get *** 2.0
2.展示服务器有哪些文件 ls 1.0
3.进入服务器某文件夹 cd *** 3.0
4.上传文件到服务器 put *** 4.0
- 对客户端:
1.查看客户端本地文件 lls 5.0
2.进入客户端某文件夹 lcd *** 6.0怎么判断是否存在文件或目录