「socket编程学习笔记 | 1 入门」

本文最后更新于:2023年4月15日 晚上

socket编程学习笔记 | 1 入门

1 | Server端

大致流程:

创建socketsocket()->绑定ip和端口bind()->设置监听模式listen()->接受客户端连接accept()->发送与接受数据send() / recv()->关闭连接close()

创建socket:

int listenfd = socket(AF_INET,SOCK_STREAM,0): ->失败返回-1

e.g.

1
2
3
4
5
6
7
// fd: file description
int listenfd;
// step01: create socket()
if ( (listenfd = socket(AF_INET,SOCK_STREAM,0)) == -1) {
perror("socket");
return -1;
}

AF_INET: 协议簇, AF_INET表示Ipv4 网络协议

SOCK_STREAM: 提供双向连续且可信赖的数据流,即 TCP

PROTOCAL: 传输协议编号, 就写0

绑定地址和端口:

int bind(int sockfd, struct sockaddr * my_addr,int addrlen);->成功返回0, 失败返回-1

其中struct sockaddr_in对应:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct sockaddr_in { 
unsigned short int sin_family;
uint16_t sin_port; // 端口
struct in_addr sin_addr; // ip地址
unsigned char sin_zero[8];
};

// 其中
struct in_addr {
uint32_t s_addr;
};

//----------------------sockaddr_in和sockaddr的关系:
// sockaddr是通用的数据结构, 当协议族被指定为AF_INET时, sockaddr被定义为上面的sockaddr_in
struct sockaddr {
unsigned short int sa_family;
char sa_data[14];
};

e.g.

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
// 示例1
struct sockaddr_in servaddr{}; // servaddr: address info of server
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family = AF_INET; // 协议簇,在socket编程中只能是AF_INET。
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 任意ip地址。
//servaddr.sin_addr.s_addr = inet_addr("192.168.190.134"); // 指定ip地址。
servaddr.sin_port = htons(atoi(argv[1])); // 指定通信端口。
if (bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr)) != 0 ) {
perror("bind");
close(listenfd);
return -1;
}

//示例2
//填充地址
struct sockaddr_in srvaddr;
bzero(&srvaddr,sizeof(srvaddr));
srvaddr.sin_family=AF_INET;
srvaddr.sin_port=htons(PORT);
if(inet_aton("127.0.0.1",srvaddr.sin_addr.s_addr)==-1){
printf("addr convert error\n");
exit(1);
}
//2.绑定服务器地址和端口
if(bind(sockfd,(struct sockaddr *)&srvaddr,sizeof(struct sockaddr))==-1){
printf("bindt error\n");
exit(1);
}

将socket设置为监听模式:

int listen(int s,int backlog) -> 成功返回0, 失败返回-1

backlog参数指定同时能处理的最大连接要求, 如若连接数目达到此上限则client端将收到错误

listen()并没有真正开始接收连线, 只是设置socket为listen模式

listen()通常在socket()bind()之后调用, 接着调用accept()

e.g.

1
2
3
4
5
if (listen(listenfd,5) != 0 ) {
perror("listen");
close(listenfd);
return -1;
}

接受客户端的连接:

int accept(int s,struct sockaddr* addr,int* addrlen);->失败返回-1

结构addr的定义参考同bind

1
2
3
4
5
6
// 第4步:接受客户端的连接。
int clientfd; // 客户端的socket。
int socklen=sizeof(struct sockaddr_in); // struct sockaddr_in的大小
struct sockaddr_in clientaddr{}; // 客户端的地址信息。
clientfd=accept(listenfd,(struct sockaddr *)&clientaddr,(socklen_t*)&socklen);
printf("客户端(%s)已连接。\n",inet_ntoa(clientaddr.sin_addr));

进行通信:

int recv(int s,void *buf, int len, unsigned int flags);

int send(int s,const void * msg,int len,unsigned int falgs);

分别用于收发, 使用见下面的示例

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
// 示例1 --- recv+send
char buffer[1024];
while (1)
{
int iret;
memset(buffer,0,sizeof(buffer));
if ( (iret=recv(clientfd, buffer, sizeof(buffer), 0)) <= 0) // 接收客户端的请求报文。
{
printf("iret=%d\n",iret);
break;
}
printf("接收:%s\n",buffer);

strcpy(buffer,"ok");
if ( (iret=send(clientfd, buffer, strlen(buffer), 0)) <= 0) // 向客户端发送响应结果。
{
perror("send");
break;
}
printf("发送:%s\n",buffer);
}

//示例2 --- read+write
if(listen(sockfd,BACKLOG)==-1){
printf("listen error\n");
exit(1);
}
for(;;){
//4.接受客户端连接
sin_size=sizeof(struct sockaddr_in);
if((new_fd=accept(sockfd,(struct sockaddr *)&clientaddr,&sin_size))==-1){
printf("accept errot\n");
continue;
}
//5.接收请求
nbytes=read(new_fd,buf,MAXDATASIZE);
buf[nbytes]='\0';
printf("client:%s\n",buf);
printf("client:%d\n",nbytes);

//6.回送响应
sprintf(buf,"wellcome!");
write(new_fd,buf,strlen(buf));
//关闭socket
close(new_fd);
}
/*
recv, send和read, write的使用:
1)尽量使用recv(,,MSG_WAITALL),read必须配合while使用,否则数据量大(240*384)时数据读不完
2)编程时写入的数据必须尽快读出,否则后面的数据将无法继续写入
3)最佳搭配如下:
nbytes = recv(sockfd, buff, buff_size,MSG_WAITALL);
nbytes = send(scokfd, buff, buff_size,MSG_WAITALL);
————————————————
版权声明:本文为CSDN博主「栎枫」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/superbfly/article/details/72782264
*/

2 | Client端

创建socket

同server端:

1
2
3
4
5
6
7
// 第1步:创建客户端的socket。
int sockfd;
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("socket");
return -1;
}

向服务器发起连接请求

struct hostent *gethostbyname(const char *name);:

把ip地址或域名转换为hostent 结构体表达的地址。

参数name,域名或者主机名,例如”127.0.0.1”、”blog.roccoshi.top”等。

返回值:如果成功,返回一个hostent结构指针,失败返回NULL。

1
2
3
4
5
6
7
8
9
10
11
struct hostent // hostent源码
{
char *h_name; /* Official name of host. */
char **h_aliases; /* Alias list. */
int h_addrtype; /* Host address type. */
int h_length; /* Length of address. */
char **h_addr_list; /* List of addresses from name server. */
#ifdef __USE_MISC
# define h_addr h_addr_list[0] /* Address, for backward compatibility.*/
#endif
};

int connect(int sockfd, struct sockaddr * serv_addr, int addrlen);

connect函数用于将参数sockfd 的socket 连至参数serv_addr 指定的服务端,参数addrlen为sockaddr的结构长度。

返回值:成功则返回0,失败返回-1,错误原因存于errno 中。

connect函数只用于客户端。

如果服务端的地址错了,或端口错了,或服务端没有启动,connect一定会失败。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct hostent *h;
if ((h = gethostbyname(argv[1])) == 0) // 指定服务端的ip地址。
{
printf("gethostbyname failed.\n");
close(sockfd);
return -1;
}
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET; // protocol
servaddr.sin_port = htons(atoi(argv[2])); // 指定服务端的通信端口。
memcpy(&servaddr.sin_addr, h->h_addr, h->h_length);
if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) != 0) // 向服务端发起连接清求。
{
perror("connect");
close(sockfd);
return -1;
}

与服务器端通信

这部分和server端基本一致

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
char buffer[1024];
for (int ii = 0; ii < 3; ii++)
{
int iret;
memset(buffer, 0, sizeof(buffer));
sprintf(buffer, "这是第%d个client,编号%03d。", ii + 1, ii + 1);
if ((iret = send(sockfd, buffer, strlen(buffer), 0)) <= 0) // 向服务端发送请求报文。
{
perror("send");
break;
}
printf("发送:%s\n", buffer);

memset(buffer, 0, sizeof(buffer));
if ((iret = recv(sockfd, buffer, sizeof(buffer), 0)) <= 0) // 接收服务端的回应报文。
{
printf("iret=%d\n", iret);
break;
}
printf("接收:%s\n", buffer);
}

3 | 实验与附件

client:

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
/*
* TCP-Client
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

int main(int argc, char *argv[])
{
if (argc != 3)
{
printf("Using:./client ip port\nExample:./client 127.0.0.1 5005\n\n");
return -1;
}

int sockfd;
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("socket");
return -1;
}

struct hostent *h;
if ((h = gethostbyname(argv[1])) == 0)
{
printf("gethostbyname failed.\n");
close(sockfd);
return -1;
}
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET; // protocol
servaddr.sin_port = htons(atoi(argv[2]));
memcpy(&servaddr.sin_addr, h->h_addr, h->h_length);
if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) != 0)
{
perror("connect");
close(sockfd);
return -1;
}

char buffer[1024];
while (true) {
printf("please enter a string to server: ");
scanf("%s\n", buffer);
if (send(sockfd, buffer, strlen(buffer), 0) == -1) {
perror("send");
break;
}
memset(buffer, 0, sizeof(buffer));
if (recv(sockfd, buffer, sizeof(buffer), 0) == -1) {
perror("recv");
break;
}
printf("recv: %s\n", buffer);
if (strcmp(buffer, "bye") == 0) break;
}

close(sockfd);
}

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
/*
* TCP-Sevrer:
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <iostream>

using namespace std;

int main(int argc,char *argv[])
{
if (argc!=2)
{
cout << "add the server port. \n E.g. ./server 4567" << endl;
return -1;
}

int listenfd;
if ( (listenfd = socket(AF_INET,SOCK_STREAM,0)) == -1) {
perror("socket");
return -1;
}

struct sockaddr_in servaddr{};
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(atoi(argv[1]));
if (bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr)) != 0 ) {
perror("bind");
close(listenfd);
return -1;
}

if (listen(listenfd,5) != 0 ) {
perror("listen");
close(listenfd);
return -1;
}

int clientfd;
int socklen=sizeof(struct sockaddr_in);
struct sockaddr_in clientaddr{};
clientfd=accept(listenfd,(struct sockaddr *)&clientaddr,(socklen_t*)&socklen);
printf("client(%s)is connected.\n",inet_ntoa(clientaddr.sin_addr));

char buffer[1024];

while (true) {
bool flag = false;
memset(buffer, 0, sizeof(buffer));
if (recv(clientfd, buffer, sizeof(buffer), 0) == -1) {
perror("recv");
break;
}
printf("recv: %s\n", buffer);
if (strcmp(buffer, "bye") != 0) {
strcpy(buffer, "ok");
} else {
flag = true;
}
if (send(clientfd, buffer, strlen(buffer), 0) == -1) {
perror("send");
break;
}
if (flag) break;
}

close(listenfd);
close(clientfd);
}

reference:

bilibili: https://www.bilibili.com/video/BV11Z4y157RY?p=9&spm_id_from=pageDriver

C语言技术网: http://www.freecplus.net/44e059cca66042f0a1286eb188c51480.html

socket命令: https://wws.lanzous.com/i9OIpn8kmgj


「socket编程学习笔记 | 1 入门」
https://blog.roccoshi.top/posts/1676/
作者
RoccoShi
发布于
2021年3月23日
许可协议