experiment of socket

Sincerely recommend read this article on my Blog, the Blog will have a better reading experience.

And the code is on my Github

A. The experiment purpose

two computer hosts as server and client communicate with each other.
With socket programmning respectively with TCP and UDP, realize the following application:
1. client reads a line of characters (data) from its keyboard and sends data to server
2. server receives the data
3. server converts lowercase letters to uppercase letters , and sends the data to client
4. client receives the data and displays line on its screen

process

B. Materials

Hardware: Lenovo Legion R7000
Software: Windows11, Ubuntu18.04, Clion, Pycharm,VMware Workstation, c++, python
Network Configuration: Bridge Mode between virtual machine and physical machine

C. Procedure

0. First, we need to figure out the IP address of the server and client.

In windows, we can use 'ipconfig' to get the IP address of the server and client while in linux, we can use ifconfig to get the IP address of the server and client.(In this experiment, the server is the physical machine and the client is the virtual machine, and the network mode is bridge mode)

ipconfig
ifconfig

in this experiment, the IP address of the server is: 10.136.12.124

and the IP address of the client is: 192.168.58.130

we choose 8080 as the port of the server.

use ping to check the connection between the server and the client.

ping

if the ping is successful, we can start the socket programming.

1. Server side

1.1. Create a socket with the socket() system call.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
WORD version = MAKEWORD(2, 2);
WSADATA data;
if (WSAStartup(version, &data) != 0) {
std::cerr << "Failed to start WSA" << std::endl;
exit(1);
}
this->port_ = port;
//protocol: 0 means TCP, 1 means UDP
if(this->protocol_ == 0){
this->server_fd_ = socket(AF_INET, SOCK_STREAM, 0);
}else if(this->protocol_ == 1){
this->server_fd_ = socket(AF_INET, SOCK_DGRAM, 0);
}
if (this->server_fd_ == -1) {
std::cerr << "Failed to create socket" << std::endl;
exit(1);
}

this->server_addr_.sin_family = AF_INET; //sin means socket internet
this->server_addr_.sin_addr.s_addr = INADDR_ANY;
this->server_addr_.sin_port = htons(this->port_);

we set the verstion of the socket to 2.2, and start the WSA, then create a socket with the socket() system call. We can choose the protocol of the socket, 0 means TCP, 1 means UDP.

parameters of the socket() system call:
- domain: AF_INET(ipv4), AF_INET6, AF_UNIX, AF_UNSPEC
- type: SOCK_STREAM(tcp), SOCK_DGRAM(udp), SOCK_RAW, SOCK_SEQPACKET
- protocol: 0 means auto choose the protocol

Then we set the server address, the family is AF_INET, the address is INADDR_ANY, and the port is the port we choose(8080).

fd means file descriptor, which is an integer that is used to access the socket.

1.2. Bind the socket to an address using the bind() system call. For a server socket on the Internet, an address consists of a port number on the host machine.
1
2
3
4
5
6
7
8
9
10
this->server_addr_.sin_family = AF_INET; //sin means socket internet
this->server_addr_.sin_addr.s_addr = INADDR_ANY;
this->server_addr_.sin_port = htons(this->port_);

if (bind(this->server_fd_, (struct sockaddr *) &this->server_addr_, sizeof(this->server_addr_)) < 0) {
std::cerr << "Failed to bind socket" << std::endl;
exit(1);
}

this->client_addr_len_ = sizeof(this->client_addr_);

we set the server address again, then bind the socket to the address we set before. If the bind() system call fails, we will exit the program.

1.3. Listen for connections with the listen() system call.
1
2
3
4
5
6
7
8
9
10
if(this->protocol_ == 0) {
std::cout << "[TCP]" << std::endl;
if (listen(this->server_fd_, 10) < 0) {
std::cerr << "Failed to listen" << std::endl;
exit(1);
}
}else if(this->protocol_ == 1){
std::cout << "[UDP]" << std::endl;
}
std::cout << "Server started on port " << this->port_ << std::endl;

if the protocol is TCP, we will listen for connections with the listen() system call, the second parameter is the maximum length of the queue of pending connections, if the queue is full, the client will be refused to connect.

if the protocol is UDP, we will just print "[UDP]" because UDP is connectionless.

1.4. Accept a connection with the accept() system call. This call typically blocks until a client connects with the server.
1
2
3
4
5
6
7
8
9
10
if(this->protocol_ == 1){
std::cerr << "UDP does not support accept" << std::endl;
exit(1);
}
this->client_fd_ = ::accept(this->server_fd_, (struct sockaddr *) &this->client_addr_, &this->client_addr_len_);
if (this->client_fd_ < 0) {
std::cerr << "Failed to accept client" << std::endl;
exit(1);
}
std::cout << "Client connected" << std::endl;

UDP does not support accept, so we will exit the program if the protocol is UDP.

if the protocol is TCP, we will accept a connection with the accept() system call, the second parameter is the address of the client, the third parameter is the length of the address of the client.

1.5. Send and receive data.
1
2
3
4
5
6
7
8
9
10
11
if(this->protocol_ == 0) {
if (::send(this->client_fd_, message, strlen(message), 0) < 0) {
std::cerr << "Failed to send message" << std::endl;
exit(1);
}
}else if(this->protocol_ == 1){
if (sendto(this->server_fd_, message, strlen(message), 0, (struct sockaddr *) &this->client_addr_, sizeof(this->client_addr_)) < 0) {
std::cerr << "Failed to send message" << std::endl;
exit(1);
}
}

if the protocol is TCP, we will use send() to send the message to the client.

if the protocol is UDP, we will use sendto() to send the message to the client.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
memset(this->buffer_, 0, sizeof(this->buffer_));
if(this->protocol_ == 0){
if (recv(this->client_fd_, this->buffer_, sizeof(this->buffer_), 0) < 0) {
std::cerr << "Failed to receive message" << std::endl;
exit(1);
}
}else if(this->protocol_ == 1){
if (recvfrom(this->server_fd_, this->buffer_, sizeof(this->buffer_), 0, (struct sockaddr *) &this->client_addr_, &this->client_addr_len_) < 0) {
std::cerr << "Failed to receive message" << std::endl;
exit(1);
}
}
this->buffer_size_ = strlen(this->buffer_);
return buffer_size_;

if the protocol is TCP, we will use recv() to receive the message from the client.

if the protocol is UDP, we will use recvfrom() to receive the message from the client.

1
2
3
char* Server::GetBuffer() {
return this->buffer_;
}

we will return the buffer pointer to the client for string processing.

1.6. Close the connection.
1
2
3
4
5
void Server::Close() {
closesocket(this->client_fd_);
closesocket(this->server_fd_);
WSACleanup();
}

Just close the client_fd and server_fd, and cleanup the WSA.

~ main function

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <iostream>
#include <winsock2.h>
#include "server.h"

int protocol = 1;

void Lowercase2Uppercase(char *str) {
for (int i = 0; str[i] != '\0'; i++) {
if (str[i] >= 'a' && str[i] <= 'z') {
str[i] -= 32;
}
}
}

int main() {
Server server(8080, protocol);
server.Start();
int msg_size = 0;
while (true){
if(protocol == 0){
server.Accept();
}
msg_size = server.Receive();
if(msg_size){
if(server.GetBuffer()[0] == 'q' && server.GetBuffer()[1] == '\0'){
break;
}
std::cout << "Client: " << server.GetBuffer() << std::endl;
Lowercase2Uppercase(server.GetBuffer());
server.Send(server.GetBuffer());
std::cout << "Server: " << server.GetBuffer() << std::endl;
}
}
server.Close();
return 0;
}

we create a server object with the port and protocol we choose, then start the server.

then we use a while loop to receive the message from the client, if the message is 'q', we will break the loop.

if the message is not 'q', we will print the message from the client, convert the lowercase letters to uppercase letters, send the message to the client, and print the message from the server.

finally, we close the server.

2.Client side

We have just finished the server side with c++, now we will start the client side with python.

2.1. Create a socket with the socket() system call.
1
2
3
4
5
6
7
8
# protocol = socket.SOCK_STREAM # TCP
protocol = socket.SOCK_DGRAM # UDP
server = '127.0.0.1'
port = 8080

s = socket.socket(socket.AF_INET, protocol)
if protocol == socket.SOCK_STREAM:
s.connect((server, port))

we can choose the protocol of the socket, socket.SOCK_STREAM means TCP, socket.SOCK_DGRAM means UDP.

Then we create a socket with the socket() system call, the first parameter is the family of the socket, the second parameter is the protocol of the socket.

if the protocol is TCP, we will connect to the server with the connect() system call.

2.2. Send and receive data.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
while True:
message = input('Enter your message: ')
if protocol == socket.SOCK_DGRAM:
s.sendto(message.encode(), (server, port))
if message == 'q':
break
response, addr = s.recvfrom(1024)
print('server: '+response.decode())
continue
s.send(message.encode())
if message == 'q':
break
response = s.recv(1024)
print('server: '+response.decode())

if the protocol is UDP, we will use sendto() to send the message to the server, and use recvfrom() to receive the message from the server.

if the protocol is TCP, we will use send() to send the message to the server, and use recv() to receive the message from the server.

2.3. Close the connection.
1
s.close()

Just close the socket.

D. Results and Analysis

1. TCP

1.1. Server side
server
1.2. Client side
client

2. UDP

2.1. Server side
server-udp
2.2. Client side
client-udp

E. Conclusion

In this experiment, we have realized the communication between the server and the client with socket programming.

We have used TCP and UDP respectively, and the results show that the communication is successful.

F. Code

you can find the code here.

or the code is as follows:

server.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//
// Created by symc on 2024/11/21.
//

#ifndef NET_SOCKET_SERVER_H
#define NET_SOCKET_SERVER_H

#include <winsock2.h>
#include <iostream>

#pragma comment(lib, "ws2_32.lib")

class Server {
private:
int protocol_;
int port_; // server port_
int server_fd_; // server file descriptor
int client_fd_; // client file descriptor
struct sockaddr_in server_addr_;
struct sockaddr_in client_addr_;
int client_addr_len_;
char buffer_[1024];
int buffer_size_ = 1024;

public:
Server(int port = 8080, int protocal = 0);
void Start();
void Accept();
void Send(const char *message);
int Receive();
char* GetBuffer();
void Close();
~Server(){
Close();
}
};

#endif //NET_SOCKET_SERVER_H

server.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
//
// Created by symc on 2024/11/21.
//

#include "server.h"
#include <winsock2.h>

#pragma comment(lib, "ws2_32.lib")


Server::Server(int port, int protocal) {
this->protocol_ = protocal;
WORD version = MAKEWORD(2, 2);
WSADATA data;
if (WSAStartup(version, &data) != 0) {
std::cerr << "Failed to start WSA" << std::endl;
exit(1);
}
this->port_ = port;
//protocol: 0 means TCP, 1 means UDP
if(this->protocol_ == 0){
this->server_fd_ = socket(AF_INET, SOCK_STREAM, 0);
}else if(this->protocol_ == 1){
this->server_fd_ = socket(AF_INET, SOCK_DGRAM, 0);
}
if (this->server_fd_ == -1) {
std::cerr << "Failed to create socket" << std::endl;
exit(1);
}

this->server_addr_.sin_family = AF_INET; //sin means socket internet
this->server_addr_.sin_addr.s_addr = INADDR_ANY;
this->server_addr_.sin_port = htons(this->port_);

if (bind(this->server_fd_, (struct sockaddr *) &this->server_addr_, sizeof(this->server_addr_)) < 0) {
std::cerr << "Failed to bind socket" << std::endl;
exit(1);
}

this->client_addr_len_ = sizeof(this->client_addr_);

}
void Server::Start() {
if(this->protocol_ == 0) {
std::cout << "[TCP]" << std::endl;
if (listen(this->server_fd_, 10) < 0) {
std::cerr << "Failed to listen" << std::endl;
exit(1);
}
}else if(this->protocol_ == 1){
std::cout << "[UDP]" << std::endl;
}
std::cout << "Server started on port " << this->port_ << std::endl;
}

void Server::Accept() {
if(this->protocol_ == 1){
std::cerr << "UDP does not support accept" << std::endl;
exit(1);
}
this->client_fd_ = ::accept(this->server_fd_, (struct sockaddr *) &this->client_addr_, &this->client_addr_len_);
if (this->client_fd_ < 0) {
std::cerr << "Failed to accept client" << std::endl;
exit(1);
}
std::cout << "Client connected" << std::endl;
}

void Server::Send(const char *message) {
if(this->protocol_ == 0) {
if (::send(this->client_fd_, message, strlen(message), 0) < 0) {
std::cerr << "Failed to send message" << std::endl;
exit(1);
}
}else if(this->protocol_ == 1){
if (sendto(this->server_fd_, message, strlen(message), 0, (struct sockaddr *) &this->client_addr_, sizeof(this->client_addr_)) < 0) {
std::cerr << "Failed to send message" << std::endl;
exit(1);
}
}
}

int Server::Receive() {
memset(this->buffer_, 0, sizeof(this->buffer_));
if(this->protocol_ == 0){
if (recv(this->client_fd_, this->buffer_, sizeof(this->buffer_), 0) < 0) {
std::cerr << "Failed to receive message" << std::endl;
exit(1);
}
}else if(this->protocol_ == 1){
if (recvfrom(this->server_fd_, this->buffer_, sizeof(this->buffer_), 0, (struct sockaddr *) &this->client_addr_, &this->client_addr_len_) < 0) {
std::cerr << "Failed to receive message" << std::endl;
exit(1);
}
}
this->buffer_size_ = strlen(this->buffer_);
return buffer_size_;
}
char* Server::GetBuffer() {
return this->buffer_;
}
void Server::Close() {
closesocket(this->client_fd_);
closesocket(this->server_fd_);
WSACleanup();
}

main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <iostream>
#include <winsock2.h>
#include "server.h"

int protocol = 1;

void Lowercase2Uppercase(char *str) {
for (int i = 0; str[i] != '\0'; i++) {
if (str[i] >= 'a' && str[i] <= 'z') {
str[i] -= 32;
}
}
}

int main() {
Server server(8080, protocol);
server.Start();
int msg_size = 0;
while (true){
if(protocol == 0){
server.Accept();
}
msg_size = server.Receive();
if(msg_size){
if(server.GetBuffer()[0] == 'q' && server.GetBuffer()[1] == '\0'){
break;
}
std::cout << "Client: " << server.GetBuffer() << std::endl;
Lowercase2Uppercase(server.GetBuffer());
server.Send(server.GetBuffer());
std::cout << "Server: " << server.GetBuffer() << std::endl;
}
}
server.Close();
return 0;
}

we should also add the ws2_32.lib in the CMakeLists.txt file.

1
target_link_libraries(net_socket ws2_32)

client.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import socket

# protocol = socket.SOCK_STREAM # TCP
protocol = socket.SOCK_DGRAM # UDP
server = '10.136.12.124'# 10.136.12.124
port = 8080

s = socket.socket(socket.AF_INET, protocol)
if protocol == socket.SOCK_STREAM:
s.connect((server, port))

while True:
message = input('Enter your message: ')
if protocol == socket.SOCK_DGRAM:
s.sendto(message.encode(), (server, port))
if message == 'q':
break
response, addr = s.recvfrom(1024)
print('server: '+response.decode())
continue
s.send(message.encode())
if message == 'q':
break
response = s.recv(1024)
print('server: '+response.decode())

s.close()

experiment of socket
https://symcreg.github.io/2024/11/21/experiment-of-socket/
作者
sam
发布于
2024年11月21日
许可协议