服务器建立socket,能够等待客户端的连接,读取发来的信息并发送回应
代码实现如下:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <stdlib.h>
#include <arpa/inet.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;
//每次清空之后再配置
memset(&s_addr,0,sizeof(struct sockaddr_in));
memset(&c_addr,0,sizeof(struct sockaddr_in));
// socket() 创建一个socket,该socket与本地的 8989 端口绑定,并阻塞式等待接收客户端的连接
s_fd = socket(AF_INET, SOCK_STREAM, 0);
if(s_fd == -1)
{
perror("socket error");
exit(1);
}else{
printf("socket success\n");
}
//配置socket 设置本地要绑定的IP地址和端口号,通过传参的形式来设置
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(atoi(argv[2]));
inet_aton(argv[1],(struct in_addr *)&s_addr.sin_addr);
// bind()
int ret = bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
if(ret == -1){
perror("bind error\n");
exit(1);
}else{
printf("bind success\n");
}
// listen() 通知内核可以连接10个客户端
listen(s_fd,10);
int clen = sizeof(struct sockaddr_in);
while(1)
{
// accept() 连接到已完成TCP握手的客户端
c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&clen);
if(c_fd == -1)
{
perror("accept");
}else{
printf("get connect\n");
}
//打印成功连接的消息,并且获取连接上来的客户端IP
printf("get connect:%s\n",inet_ntoa(c_addr.sin_addr));
//客户端接入之后,创建进程不断读取客户端发来的消息
// 父进程负责读取消息
if(fork() == 0)
{
//子进程负责写入消息
if(fork() == 0)
{
while(1)
{
memset(msg,0,sizeof(msg));
printf("input:\n");
scanf("%s",msg);
// write()
write(c_fd,msg,strlen(msg));
}
}
while(1)
{
memset(readbuf,0,sizeof(readbuf));
//read() 等待读取对方发来的信息,如果接收不到消息会一直阻塞在这边
n_read = read(c_fd,readbuf,128);
if(n_read == -1)
{
perror("read error");
exit(1);
}else{
printf("get the message form client:%s\n",readbuf);
}
}
}
}
return 0;
}
客户端建立socket,连接服务器,并不断与其进行消息对话
代码实现如下:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
int c_fd;
int n_read;
char readbuf[128];
char msg[128] = {0};
struct sockaddr_in c_addr;
//每次清空之后再配置
memset(&c_addr,0,sizeof(struct sockaddr_in));
// socket() 创建一个socket,该socket与本地的 8989 端口绑定,并阻塞式等待接收客户端的连接
c_fd = socket(AF_INET, SOCK_STREAM, 0);
if(c_fd == -1)
{
perror("socket error");
exit(1);
}else{
printf("socket success\n");
}
//配置socket 设置本地要绑定的IP地址和端口号
c_addr.sin_family = AF_INET;
c_addr.sin_port = htons(atoi(argv[2]));
inet_aton(argv[1],(struct in_addr *)&c_addr.sin_addr);
//connect()
connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr_in));
//打印成功连接的消息,并且获取连接上来的客户端IP
printf("get connect:%s\n",inet_ntoa(c_addr.sin_addr));
while(1)
{
//fork() 创建父进程负责接收服务器端返回的消息
if(fork() == 0)
{
//子进程负责向服务器端发送消息
if(fork() == 0)
{
while(1)
{
memset(msg,0,sizeof(msg));
printf("please input message to server:");
scanf("%s",msg);
// write()
write(c_fd,msg,strlen(msg));
}
}
}
while(1)
{
memset(readbuf,0,sizeof(readbuf));
// read() 不断读取服务器端返回的信息
n_read = read(c_fd,readbuf,128);
if(n_read == -1)
{
perror("read error");
exit(1);
}else{
printf("get message from server,readbuf:%s\n",readbuf);
}
}
}
return 0;
}
怎么理解socket
为什么要使用socket?
目前很多开发都不是基于单机了,跑在一台Linux系统里面的协调工作使用进程间通信,但是多机通信就不够用了,要进行外部的数据收发,得通过网络编程来实现。
进程间通信的方式:管道、消息队列、共享内存、信号量、信号——它们的特点是依赖于内核,存在缺陷——可能无法进行多机通信。
socket的使用流程
服务器:socket->bind->listen->accept->read->write->close
客户端:socket ->connect->write->read->close
服务器进程运行起来之后,创建socket,该socket与本地的端口进行捆绑,会阻塞式等待客户端的连接;
客户端创建本地socket,同样捆绑到本地端口,通过指定服务器的IP地址和端口号进行连接;
服务器接收客户端的请求,解除阻塞,返回已连接的socket描述符。
一个服务器通常仅创建一个监听socket,它在该服务器的生命周期内一直存在。
为什么知道IP地址和端口号很重要?
因为网络编程依赖地址和数据。
- 地址包括IP地址和端口号。
举例:有一台IP地址指向的电脑上跑很多服务,有不同的进程,如ftp\http\socket,此时客户端以socket方式接入,到底跟哪个服务器对接要通过端口号来确定。
- 端口号的作用
一台拥有IP地址的主机可以提供Web、FTP、SMTP等服务,那么IP地址和网络服务的关系是一对多的,主机不能单单靠IP地址来区分不同的网络服务。
端口提供一种访问通道,主机可以通过“IP地址+端口号”来区分不同的服务。
- 数据包括协议
socket中主要了解TCP 面向连接,可靠,做精细控制的时候使用,不容易出错
UDP 面向报文,不可靠,内存响应快、数据量大,像视频这种可以丢失一些数据,人眼观测不到、影响不大可以使用。
-
字节序
小端字节序:低序字节存储在起始地址,计算机内存中的存储顺序,如X86系列CPU
大端字节序:高序字节存储在起始地址,网络字节序
拓展思考——如何在多方消息收发时,明确光标所在的客户端位置?
代码实现如下:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
int s_fd;
int c_fd;
int n_read;
int mark = 0;
char readbuf[128];
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));
// socket() 创建一个socket,该socket与本地的 8989 端口绑定,并阻塞式等待接收客户端的连接
s_fd = socket(AF_INET, SOCK_STREAM, 0);
if(s_fd == -1)
{
perror("socket error");
exit(1);
}else{
printf("socket success\n");
}
//配置socket 设置本地要绑定的IP地址和端口号,通过传参的形式来设置
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(atoi(argv[2]));
inet_aton(argv[1],(struct in_addr *)&s_addr.sin_addr);
// bind()
int ret = bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
if(ret == -1){
perror("bind error\n");
exit(1);
}else{
printf("bind success\n");
}
// listen() 通知内核可以连接10个客户端
listen(s_fd,10);
int clen = sizeof(struct sockaddr_in);
while(1)
{
// accept() 连接到已完成TCP握手的客户端
c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&clen);
if(c_fd == -1)
{
perror("accept");
}else{
printf("get connect\n");
}
mark++;
//打印成功连接的消息,并且获取连接上来的客户端IP
printf("get connect:%s\n",inet_ntoa(c_addr.sin_addr));
//客户端接入之后,创建进程不断读取客户端发来的消息
// 父进程负责读取消息
if(fork() == 0)
{
//子进程负责写入消息
if(fork() == 0)
{
while(1)
{
//明确当前是与哪个客户端进行通信,且此处是自动回复给客户端
sprintf(msg,"hello client%d\n",mark);
// write()
write(c_fd,msg,strlen(msg));
}
}
while(1)
{
memset(readbuf,0,sizeof(readbuf));
//read() 等待读取对方发来的信息,如果接收不到消息会一直阻塞在这边
n_read = read(c_fd,readbuf,128);
if(n_read == -1)
{
perror("read error");
exit(1);
}else{
printf("get the message form client:%s\n",readbuf);
}
}
}
}
return 0;
}
sprintf的使用
- int sprintf(char *str, const char *format, ...)
- 将一个格式化的字符串输出到目的字符串中
- 而printf是将字符串输出到屏幕上
标志位的使用
每当有新的客户端输入时,都通过标志位来添加序号。每个客户端都有自己的编号,服务器在发送消息时,就知道在与哪个客户端聊天了。