文章目录
一、为什么需要网络编程?
需要实现多机通讯
回射客户/服务器
- 当套接字被创建后,它的端口号和IP地址都是空的,因此应用进程要调用bind(绑定)来指明套接字的本地地址。在服务器端调用bind时就是把熟知端口号和本地IP地址填写到已创建的套接字中。这就叫做把本地地址绑定到套接字。
- 服务器在调用bind中,还必须调用listen(收听)把套接字设置为被动方式,以便随时接受客户的服务请求。UDP服务器由于只提供无连接服务,不使用listen系统调用。
- 服务器紧接着就调用accept(接受),以便把远端客户进程发来的连接请求(connect)[主动方式]提取出来。系统调用accept的一个变量就是要指明从哪个套接字发起的连接。
二、Sockt服务器和客户端开发步骤
服务器端
socket
网络上的两个应用程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为socket。socket可以看成是用户进程与内核网络协议栈的编程接口。socket可以看成两个进程之间通信的抽象。socket是全双工的通信方式。
socket不仅可以用于本机的进程间通信,还可以用于网络上不同主机(异构也可以)的进程间通信。
1.创建套接字
创建套接字的作用
1.当应用进程需要使用网络进行通信时就发出系统调用,请求操作系统为其创建”套接字”,以便把网络通信所需要的系统资源分配给该应用进程。
2.操作系统为这些资源的总和用一个叫做套接字描述符的号码来表示,并把此号码返回给应用进程。应用进程所进行的网络操作都必须使用这个号码。
3.通信完毕后,应用进程通过一个关闭套接字的系统调用通知操作系统回收与该”号码”相关的所有资源。
socket函数
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
// domain:指定通信协议族(protocol family)。常用的协议族有AF_INET、AF_INET6、AF_LOCAL、AF_ROUTE等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用IPV4地址与端口号的组合。
// type:指定socket类型。流式套接字(SOCK_STREAM)是一种面向连接的Socket,针对于面向连接的TCP服务应用;数据报套接字(SOCK_DGRAM),针对于无连接的UDP服务应用;原始套接字(SOCK_RAW)。
// protocol:协议类型。常用的协议有IPPROTO_TCP(Tcp传输协议)、IPPROTO_UDP(Udp传输协议)、IPPROTO_STCP(Stcp传输协议)、IPPROTO_TIPC(Tipc传输协议)等。
返回值:成功返回非负整数,也就是套接口描述字,简称套接字,失败返回-1.每一个进程的进程空间里都有一个套接字描述符表,该表中存放着套接字描述符和套接字数据结构对应的关系。该表中有一个字段存放新创建的套接字的描述符,另一个字段存放套接字数据结构的地址,因此根据套接字描述符就可以找到其对应的套接字数据结构。
2.准备地址
bind函数
功能:绑定一个本地地址到套接字
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
参数:sockfd:socket函数返回的套接字
addr:要绑定的地址
addrlen:地址长度
3.地址转换
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
/*
功能:将一个字符串IP地址转换为32为的网络序列IP地址。
返回值:函数成功,函数返回值非零,如果输入地址不正确会返回零。
参数1:输入参数 cp const char* 点分十进制
参数2:输出参数 in struct in_addr 无符号长整型
aton表示ascii码转换为net 也就是点分十进制转换为网络字节序
*/
int inet_aton(const char *cp, struct in_addr *inp);
/*
返回值:若字符串有效则将字符串转换为无符号长整型格式,否则为INADDR_NONE
*/
in_addr_t inet_addr(const char *cp); // 点分十进制IP转换成十进制
/*
返回值:无符号长整型转换为ascii码格式,也就是点分十进制
*/
char *inet_ntoa(struct in_addr in); // 十进制转换成点分十进制,输出是字符串格式
/* Internet address. */
struct in_addr {
uint32_t s_addr; /* address in network byte order */
};
4.监听
listen函数
功能:将套接字用于监听进入的连接
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int listen(int sockfd, int backlog);
参数1:sockfd:socket函数返回的套接字
参数2:backlog:规定内核为此套接字排队的最大连接个数
返回值:成功返回0 失败返回-1
一般来说,listen函数应该在调用socket和bind函数之后,调用函数accept之前调用。
对于给定的监听套接口,内核要维护两个队列:
- 已由客户端发出并到达服务器,服务器正在等待完成相应的TCP三路握手过程
- 已完成连接的队列
5.连接
accept函数
功能:从已完成连接队列返回第一个连接,如果已完成连接队列为空,则阻塞。
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
// 参数1:sockfd 服务器套接字
// 参数2:addr 将返回对等方的套接字地址
// 参数3:addrlen 返回对等方的套接字地址长度
// 返回值:成功返回非负整数(客户端文件描述符),失败返回-1
数据的收发
read函数
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
参数1:文件描述符
参数2:读取到的字节数放置的地址buf
参数3:请求从文件描述符fd读取的字节数,读取到buf
返回值:成功,返回读取的字节数;如果缓冲区中的字节数不够,读取的字节数可能比count个字节小少。错误,返回-1,同时errno将会被设置。同时文件的读写位置向后移。
write函数
功能:从buffer中写count个字节到打开的文件中(fd表示打开的文件)
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
参数1:文件描述符
参数2:将要被写出到其他文件的缓冲区内容
参数3:count表示写出字节数
返回值:成功,写出的字节数返回(0表示没有可以写的了),如果写出的字节数比count小,可能是buf中没有count个字节;错误,返回-1.
write成功返回,只是buf中的数据被复制到内核中的TCP发送缓冲区。置于数据什么时候被发往网络,什么时候被对方主机接收,什么时候被对方进程读取,系统调用层面不会给予任何保证和通知。
服务器端代码演示
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
int main()
{
//1.socket
int s_fd;
struct sockaddr_in s_addr; //定义服务器地址结构体
struct sockaddr_in c_addr; //客户端地址结构体
int n_read;
char readBuf[128]={0};
char *wr="it's for server";
memset(&s_addr,0,sizeof(struct sockaddr_in));
memset(&c_addr,0,sizeof(struct sockaddr_in));
s_fd=socket(AF_INET,SOCK_STREAM,0); //第一步创建套接字 以及套接字描述符 后续其他操作依靠描述符来操作 和 文件描述符类似
if(s_fd==-1){ //如果等于-1 创建失败
perror("socket"); //调用perror函数 显示错误原因
exit(-1);
}
//2.bind
s_addr.sin_family=AF_INET; //结构体第一个属性 为 网络属性 上面有介绍
s_addr.sin_port=htons(9809); //定义端口号 需注意 将端口转为 网络字节序
//s_addr.sin_addr=192.168.0.168;
inet_aton("192.168.0.108",&s_addr.sin_addr); // 这里需要将地址ASCII转成net 第二个参数为指针 放着结构体第三个属性 IP地址
bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
//绑定地址 第二个参数是个结构体指针 包括IP地址 端口信息 第三个为结构体大小
//3.listen
listen(s_fd,10); //监听 第二个参数 为 监听数量
//4.accept
int clen=sizeof(struct sockaddr_in);
int c_fd=accept(s_fd,(struct sockaddr *)&c_addr,&clen); //从已完成连接队列返回第一个连接,如果已完成连接队列为空,则阻塞。
//第二个参数 和bind类似 第三个参数 是个指针 放对接地址的长度
//成功返回客户端的文件描述符 失败返回-1
if(c_fd==-1){
perror("accept");
}
printf("get connect ip:%s\n",inet_ntoa(c_addr.sin_addr)); //将对接地址的net转成ASCII
//read
n_read=read(c_fd,readBuf,128); //将c_fd内容读到readbuf
if(n_read==-1){
perror("read");
}else{
printf("get message size:%d,%s\n",n_read,readBuf); //打印readbuf
}
//write
write(c_fd,wr,strlen(wr));
return 0;
}
客户端
connect函数
// 功能:用于建立与指定socket的连接
int connect(int s, const struct sockaddr * name, int namelen);
参数1:s 标识一个未连接的socket
参数2:name 指向要连接套接字的sockaddr结构体指针,也就是sockaddr中是要连接的服务器端的地址。
参数3:sockaddr 结构体的字节长度
客户端代码演示
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
int main()
{
//1.socket
int c_fd;
struct sockaddr_in c_addr;
int n_read;
char readBuf[128]={0};
char *wr="it's for client";
memset(&c_addr,0,sizeof(struct sockaddr_in));
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(9809);
//s_addr.sin_addr=192.168.0.168;
inet_aton("192.168.0.108",&c_addr.sin_addr);
//connect
if(connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr_in))==-1){
//连接 第二三个参数 和bind类似
perror("connect");
exit(-1);
}
printf("get connect ip:%s\n",inet_ntoa(c_addr.sin_addr));
write(c_fd,wr,strlen(wr)); //往c_fd写入
//read
n_read=read(c_fd,readBuf,128);
if(n_read==-1){
perror("read");
}else{
printf("get message size:%d,%s\n",n_read,readBuf);
}
//write
return 0;
}
实现多机通讯
server
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
int main(int argc,char **argv) //这里用命令行参数 带代替输入 IP地址 端口号
{
//1.socket
int s_fd;
int c_fd;
struct sockaddr_in s_addr;
struct sockaddr_in c_addr;
int n_read;
char readBuf[128]={0};
//char *wr="it's for server";
char wr[128]={0};
int mark=0;
if(argc !=3){ //做个判断 防止少输 多输
perror("arg");
exit(-1);
}
memset(&s_addr,0,sizeof(struct sockaddr_in));
memset(&c_addr,0,sizeof(struct sockaddr_in));
s_fd=socket(AF_INET,SOCK_STREAM,0);
if(s_fd==-1){
perror("socket");
exit(-1);
}
//2.bind
s_addr.sin_family=AF_INET;
s_addr.sin_port=htons(atoi(argv[2])); //这里将命令行输入的字符串转成int型 再通过htons转成网络字节序
//s_addr.sin_addr=192.168.0.168;
inet_aton(argv[1],&s_addr.sin_addr);
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){//做个循环不停的accept connect的请求
c_fd=accept(s_fd,(struct sockaddr *)&c_addr,&clen);
if(c_fd==-1){
perror("accept");
}
printf("get connect ip:%s\n",inet_ntoa(c_addr.sin_addr));
//read
mark++; // 定义mark变量 显示这是第几个客户端
if(fork()==0){
if(fork()==0){
while(1){
memset(wr,0,sizeof(wr)); //循环会不停的调用wr 里面的内容要重置 防止覆盖
sprintf(wr,"welcome NO.%d Client\n",mark); //服务端每隔3秒打印 写入wr
write(c_fd,wr,sizeof(wr)); //写入 供客户端读取
sleep(3);
}
}
while(1){
memset(readBuf,0,sizeof(readBuf)); //读也要重置
n_read=read(c_fd,readBuf,128);
if(n_read==-1){
perror("read");
}else{
printf("get message size:%d,%s\n",n_read,readBuf);
}
//break;
}
}
}
return 0;
}
client
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
int main(int argc,char ** argv)
{
//1.socket
int c_fd;
struct sockaddr_in c_addr;
int n_read;
char readBuf[128]={0};
char wr[128]={0};
if(argc !=3){
perror("arg");
exit(-1);
}
memset(&c_addr,0,sizeof(struct sockaddr_in));
c_fd=socket(AF_INET,SOCK_STREAM,0);
if(c_fd==-1){
perror("socket");
exit(-1);
}
//2.bind
c_addr.sin_family=AF_INET;
c_addr.sin_port=htons(atoi(argv[2]));
//s_addr.sin_addr=192.168.0.168;
inet_aton(argv[1],&c_addr.sin_addr);
//3.connect
if(connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr_in))==-1){
perror("connect");
exit(-1);
}
printf("get connect ip:%s\n",inet_ntoa(c_addr.sin_addr));
while(1){
if(fork()==0){
while(1){
memset(wr,0,sizeof(wr));
printf("clien input: ");
gets(wr);
write(c_fd,wr,strlen(wr));
}
}
while(1){
n_read=read(c_fd,readBuf,128);
if(n_read==-1){
perror("read");
}else{
printf("get message size:%d,%s\n",n_read,readBuf);
}
//write
}
}
return 0;
}