最近找实习了,发现很多岗位需要网络编程,但是在计算机网络课上又没有好好听,所以最近又开始重新学习了一下,一切从实践出发,帮助快速上手网络编程。
搭建服务端。
1. 创建服务端 Socket
int serveSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
-
AF_INET
:指定使用 IPv4 地址族,你也可以使用IPv6(AF_INET6)。 -
SOCK_STREAM
:表示使用面向连接的流式传输协议(TCP)。 -
IPPROOT_TCP
:明确使用 TCP 协议。
如果创建成功,socket
函数会返回一个套接字描述符(serveSocket
),用于后续的操作;如果失败,则返回负值,并设置相应的错误码。
2. 绑定 Socket
struct sockaddr_in sockaddr;
memset(&sockaddr, 0, sizeof(sockaddr)); // 初始化为 0
sockaddr.sin_family = AF_INET; // 设置地址簇
sockaddr.sin_addr.s_addr = inet_addr(ip.c_str()); // 设置 IP 地址
sockaddr.sin_port = htons(port); // 设置端口号,使用网络字节序
-
sockaddr_in
是一种标准的结构体类型,用于表示 IPv4 地址和端口号信息。 -
inet_addr
函数将点分十进制的 IP 地址字符串转换为网络字节序的 32 位无符号整数。 -
htons
函数将主机字节序的短整型数值转换为网络字节序。
绑定操作是将套接字与特定的 IP 地址和端口号关联起来,以便客户端能够通过该地址和端口与服务器进行通信。如果绑定失败,可能是由于端口已被占用、IP 地址不正确等原因。
3.监听 Socket
if (listen(serveSocket, 1024) < 0) {
cout << "listen failed" << endl;
return -1;
} else {
cout << "socket listen..." << endl;
}
-
listen
函数将已绑定的套接字转换为被动套接字,使其可以接受传入的连接请求。 -
第二个参数
1024
表示监听队列的最大长度,即操作系统可以同时保存多少个未处理的连接请求。
当服务器开始监听后,就可以等待客户端的连接了。
4.接受客户端连接
int connfd = accept(serveSocket, nullptr, nullptr);
-
accept
函数从监听队列中取出一个连接请求,并返回一个新的套接字描述符(connfd
),用于与该客户端进行通信。 -
如果没有客户端连接,
accept
函数会阻塞,直到有客户端连接到来。
5.接收和发送数据
size_t len = recv(connfd, buf, sizeof(buf), 0);
send(connfd, buf, sizeof(buf), 0);
-
recv
函数用于从已连接的套接字中接收数据,将接收到的数据存储到缓冲区buf
中,并返回接收到的字节数。如果对端关闭了连接,返回值为 0。 -
send
函数用于向已连接的套接字发送数据,从缓冲区buf
中读取数据并发送出去。 -
buf的定义为char buf[1024]。
通过接收和发送数据,服务器和客户端之间就可以实现双向通信了。
6.关闭 Socket
close(serveSocket);
在完成通信后,需要关闭套接字以释放系统资源。如果服务器需要持续运行并接受多个客户端连接,可以将相关代码放入循环中,并在适当的时候关闭套接字。
搭建客户端
1. 创建客户端 Socket
int clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
客户端创建 Socket 的方式与服务器类似,也是指定地址族、套接字类型和协议。
2. 连接服务端
struct sockaddr_in sockaddr;
memset(&sockaddr, 0, sizeof(sockaddr));
sockaddr.sin_family = AF_INET;
sockaddr.sin_addr.s_addr = inet_addr(ip.c_str());
sockaddr.sin_port = htons(port);
if (connect(clientSocket, (struct sockaddr *)&sockaddr, sizeof(sockaddr)) < 0) {
cout << "connect failed" << endl;
return -1;
} else {
cout << "connect success" << endl;
}
-
客户端通过
connect
函数主动连接服务器,指定服务器的 IP 地址和端口号。 -
如果连接成功,就可以与服务器进行通信了;如果连接失败,可能是由于服务器未启动、IP 地址或端口号错误等原因。
3. 发送和接收数据
string result = to_string(c) + data;
if (send(clientSocket, result.c_str(), result.size(), 0) < 0) {
cout << "error" << endl;
return -1;
}
memset(buf, 0, sizeof(buf));
recv(clientSocket, buf, sizeof(buf), 0);
cout << buf << endl;
-
客户端通过
send
函数向服务器发送数据,将数据拼接成字符串后发送出去。 -
通过
recv
函数接收服务器返回的数据,并将其打印出来。
4.关闭 Socket
close(clientSocket);
在完成通信后,客户端也需要关闭套接字。
你还可以在客户端增加接收服务端的消息,在服务端增加发送消息的代码,也就是用几次send函数,我在上面也就没多写了,在下面的完整代码中有。
完整代码
服务端
#include <iostream>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>//inet_addr()
#include <netinet/in.h>//有IPPROTO_TCP
#include <unistd.h>//close
#include <cstring>
using namespace std;
int main(){
//第一步,创建服务端socket
int serveSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);//SOCK_STREAM是TCP,SOCK_DGRAM是UDP
if (serveSocket < 0) {
cout << "Socket creation failed" << endl;
return -1;
}else{
cout<<"create socket success"<<endl;
}
//第二步,绑定socket
string ip="192.168.66.132";//自己服务端的ip
int port=50000;//自己服务端的端口号
struct sockaddr_in sockaddr;//sockaddr_in 是一种标准的结构体类型,用于表示 IPv4 地址和端口号信息
memset(&sockaddr,0,sizeof(sockaddr));//初始化为0
sockaddr.sin_family=AF_INET;//设置地址簇
sockaddr.sin_addr.s_addr=inet_addr(ip.c_str());
sockaddr.sin_port=htons(port);
if(bind(serveSocket,(struct sockaddr *)&sockaddr,sizeof(sockaddr))<0){//将一个套接字(socket)绑定到指定的本地地址和端口。
cout<<"bind failed"<<endl;
return -1;
}else{
cout<<"bind success"<<endl;
}
//第三步,监听socket
//listen() 是一个系统调用,用于将一个已绑定的套接字(通过 bind())转换为被动套接字(即监听套接字),以便它可以接受传入的连接请求。
//1024 是传递给 listen() 的第二个参数 backlog,表示监听队列的最大长度。这决定了操作系统可以同时保存多少个未处理的连接请求。
if(listen(serveSocket,1024)<0){
cout<<"listen failed"<<endl;
return -1;
}else{
cout<<"socket listen..."<<endl;
}
char buf[1024]={0};//用于接受客户端发送过来的信息
while(true){
//第四步,接受客户端连接
int connfd=accept(serveSocket,nullptr,nullptr);//调用 accept() 函数从 serveSocket 的监听队列中取出一个连接请求,并返回一个新的套接字描述符 connfd
//connfd 是专门为该客户端创建的套接字描述符,包含与客户端通信所需的所有必要信息。
if(connfd<0){
cout<<"accept socket failed"<<endl;
return -1;
}
while(true){
memset(buf, 0, sizeof(buf)); // 清空缓冲区
//第五步,接受客户端的数据
size_t len=recv(connfd,buf,sizeof(buf),0);//通过 recv() 函数从已连接的套接字 connfd 中接收数据,并将其存储到缓冲区 buf 中
//成功时返回接收到的字节数。如果对端关闭了连接,返回值为 0
cout<<"connfd:"<<connfd<<" "<<"buf:"<<buf<<endl;
// 根据 buf[0] 的值进行分支判断
string response;
switch(buf[0]){
case '1': response = "Response for option 1"; break;
case '2': response = "Response for option 2"; break;
case '3': response = "Response for option 3"; break;
case '0':
response = "Goodbye";
send(connfd,response.c_str(), response.length(),0);
close(connfd);
goto break_label;
break;
default: response = "Invalid option";
}
//第六步,向客户端发送数据
send(connfd,response.c_str(),response.length(),0);
break_label:
break;
}
}
//第七步,关闭socket
close(serveSocket);
return 0;
}
客户端
#include <iostream>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>//有IPPROTO_TCP
#include <unistd.h>//close
#include <cstring>
using namespace std;
int main(){
//第一步,创建socket
int clientSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(clientSocket<0){
cout<<"socket create failed"<<endl;
return -1;
}else{
cout<<"socket create success"<<endl;
}
//第二步,连接服务端
string ip="192.168.66.132";//服务端的ip
int port=50000;//服务端的端口
struct sockaddr_in sockaddr;
memset(&sockaddr,0,sizeof(sockaddr));
sockaddr.sin_family=AF_INET;
sockaddr.sin_addr.s_addr=inet_addr(ip.c_str());//inet_addr 的作用:将点分十进制的 IPv4 地址字符串转换为网络字节序的 32 位无符号整数
sockaddr.sin_port=htons(port);//htons(port) 是一个用于将主机字节序(Host Byte Order)的短整型数值转换为网络字节序(Network Byte Order)
if(connect(clientSocket,(struct sockaddr *)&sockaddr,sizeof(sockaddr))<0){
cout<<"connect failed"<<endl;
return -1;
}else{
cout<<"connect success"<<endl;
}
string data;
int c;
char buf[1024]={0};
//第三步,向服务端发送数据
while(true){
cout<<"请选择:1.... 2.... 3.... 0.退出"<<endl;
cin>>c;
cin.ignore(); // 清除缓冲区中的换行符
cout<<"请输入要传送的内容"<<endl;
getline(cin, data);
string result=to_string(c)+data;
data.clear();
if(send(clientSocket,result.c_str(),result.size(),0)<0){
cout<<"error"<<endl;
return -1;
}
if(c==0){
break;
}
//第四步,接受服务端消息
memset(buf, 0, sizeof(buf)); // 清空缓冲区
recv(clientSocket,buf,sizeof(buf),0);
cout<<buf<<endl;
}
//第五步,关闭socket
close(clientSocket);
}