一、引言
传统的进程间通信借助内核提供的 IPC 机制进行, 但是只能限于本机通信, 若
要跨机通信, 就必须使用网络通信,比如之前在操作系统学习到的pipe通信,这是一个本机通信,是最基本的IPC机制进行的。
socket网络通信和pipe通信的区别在于:
二、socket 编程预备知识
比如:0x12345678 如何存放?
将十六进制数 0x12345678 拆分成四个字节,分别为 0x12、0x34、0x56、0x78。
按照大端:0x12345678。
按照小端:0x78563412
如何测试本机是否是大端还是小端
#include <stdio.h>
int main() {
union {
int i;
char c[4];
} endianTest;
endianTest.i = 1;
if (endianTest.c[0] == 1) {
printf("本机是小端模式\n");
} else {
printf("本机是大端模式\n");
}
return 0;
}
网络传输用的是大端法, 如果机器用的是小端法, 则需要进行大小端的转换.
下面 4 个函数就是进行大小端转换的函数:
IP地址转换函数:
(1)int inet_pton(int af, const char *src, void *dst);
手工也可以计算: 如192.168.232.145, 先将4个正数分别转换为16进制数,
192--->0xC0 168--->0xA8 232--->0xE8 145--->0x91
最后按照大端字节序存放: 0x91E8A8C0, 这个就是4字节的整形值.
(2)const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
例如: IP地址为010aa8c0, 转换为点分十进制的格式:
01---->1 0a---->10 a8---->168 c0---->192
由于从网络中的IP地址是高端模式, 所以转换为点分十进制后应该为: 192.168.10.1
通过man 7 ip可以查看相关说明:
三、主要socket函数API介绍
(1)int socket(int domain, int type, int protocol);
(2)int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
(3)int listen(int sockfd, int backlog);
(4)int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
从已连接队列中获取一个新的连接, 并获得一个新的文件描述符, 该文件描述符用于和客户端通信. (内核会负责将请求队列中的连接拿到已连接队列中)
(5)int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
接下来就可以使用write和read函数进行读写操作了.
除了使用read/write函数以外, 还可以使用recv和send函数
读取数据和发送数据:
对应recv和send这两个函数flags直接填0就可以了.
注意: 如果写缓冲区已满, write也会阻塞, read读操作的时候, 若读缓冲区没有数据会引起阻塞.
使用socket的API函数编写服务端和客户端程序的步骤图示:
测试过程中可以使用netstat命令查看监听状态和连接状态
netstat命令:
a表示显示所有,
n表示显示的时候以数字的方式来显示
p表示显示进程信息(进程名和进程PID)
常用的netstat -anp | grep 8888 可以查看端口为8888的网络连接状态
四、服务端和客户端代码
客户端代码:
//客户端代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
int main()
{
//创建socket---用于和服务端进行通信
int cfd = socket(AF_INET, SOCK_STREAM, 0);
if(cfd<0)
{
perror("socket error");
return -1;
}
//连接服务端
//int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
struct sockaddr_in serv;
serv.sin_family = AF_INET;
serv.sin_port = htons(8888);
inet_pton(AF_INET, "127.0.0.1", &serv.sin_addr.s_addr);
printf("[%x]\n", serv.sin_addr.s_addr);
int ret = connect(cfd, (struct sockaddr *)&serv, sizeof(serv));
if(ret<0)
{
perror("connect error");
return -1;
}
int n = 0;
char buf[256];
while(1)
{
printf("请输入字符串:\n");
//读标准输入数据
memset(buf, 0x00, sizeof(buf));
n = read(STDIN_FILENO, buf, sizeof(buf));
//发送数据
write(cfd, buf, n);
//读服务端发来的数据
memset(buf, 0x00, sizeof(buf));
n = read(cfd, buf, sizeof(buf));
if(n<=0)
{
printf("read error or server closed, n==[%d]\n", n);
break;
}
printf("转化为大写的符串为:%s\n",buf);
}
//关闭套接字cfd
close(cfd);
return 0;
}
服务端代码:
//服务端程序
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <ctype.h>
int main()
{
int lfd = socket(AF_INET,SOCK_STREAM,0);
if(lfd < 0)
{
perror("socket error");
return -1;
}
struct sockaddr_in serv;
bzero(&serv,sizeof(serv));
serv.sin_family = AF_INET;
serv.sin_port = htons(8888);
serv.sin_addr.s_addr = htonl(INADDR_ANY);
int ret = bind(lfd,(struct sockaddr *)&serv,sizeof(serv));
if(ret<0)
{
perror("bind error");
return -1;
}
listen(lfd,128);
struct sockaddr_in client;
socklen_t len = sizeof(client);
int cfd = accept(lfd,(struct sockaddr*)&client,&len);
int i;
int n;
char buf[1024];
while(1)
{
//读数据
memset(buf,0x00,sizeof(buf));
n = read(cfd,buf,sizeof(buf));
if(n <= 0)
{
printf("read error or client close n == [%d]\n",n);
break;
}
printf("n == [%d],buf == [%s]\n",n,buf);
for(i = 0;i < n;i ++)
{
buf[i] = toupper(buf[i]);
}
write(cfd,buf,n);
}
close(lfd);
close(cfd);
}
按照上面代码,先启动服务端代码,再去启动客户端代码,根据客户端代码提示,完成客户端实现大小写字母转化