一个进程的意义,或在于,它有它要执行的任务。

有的为了调度,有的为了计算,有的为了IO,有的为了传输。

一个进程的意义,或在于,它要为后续的进程创造条件。

在时空里埋下线索,为后续的crash保留好进程上下文。Debug的过程或许痛苦,但是螺旋式上升(或者下沉)必须经历。

一个进程的意义,或在于,它要代表一颗星星,静静地挂在内存的夜空。

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

链接通俗来说就是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语言

今年27了,终于明白副业是个伪命题。

从我大三实习那年起,在维持主业的同时,我就一直热衷于探索副业。

最开始在淘宝上卖乐器,在58同城倒腾二手物品,后来尝试做CPA、淘宝客、建站做Adsense,以及出教程、搞培训,炒股、炒币、买基金…

除了炒股以外,别的基本上没亏过钱。但也没有因此赚过什么钱。

过去的我,太急于开辟一条新的赛道了,以至于在主业(指网络安全)的道路上还没有做出什么成绩,就想着赶紧脱离这一条一眼忘不到头的技术路。

幻想着能够有朝一日把副业从小做大,慢慢扶正,让主业真正成为一个爱好。

然而幻想终究是一种幻想,相信许多人都会有这种幻想。

这种幻想太幼稚了。如今我终于认识到,以大部分人的天资,能够在主业的道路上有所建树,已经是莫大的成功了。

更何况我们这些搞安全的技术小子,如今能凭借这一身牢底坐穿的手艺有口饭吃,绝对要感谢老天爷。

要好好珍惜主业的机会,踏踏实实继续突破。

以后不谈副业了。

疫情

发展

转折

故事

再启程

Windows系统下C语言中使用自定义包的方法:

首先明确GOPATH的路径,执行go env | findstr GOPATH

1
2
3
4
C:\Users\CHORDER>go env | findstr GOPATH
set GOPATH=C:\Users\CHORDER\go

C:\Users\CHORDER>

接着在GOPATH目录中创建相应的自定义包,这里定义一个example包,并创建两个源码文件src1.gosrc2.go,源码内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
/* C:\Users\CHORDER\go\src\chorder.net\example\src1.go */

package example

import "fmt"

func init(){
fmt.Printf("\nchorder.net/example/src1.go init() has been called.")
}

func Src1(){
fmt.Printf("\nchorder.net/example/src1.go Src1() has been called.")
}
1
2
3
4
5
6
7
8
9
10
11
12
13
/*C:\Users\CHORDER\go\src\chorder.net\example\src2.go*/

package example

import "fmt"

func init(){
fmt.Printf("\nchorder.net/example/src2.go init() has been called.")
}

func Src2(){
fmt.Printf("\nchorder.net/example/src2.go Src1() has been called.")
}

编写代码调用这两个包中的方法:

packge_test.go

1
2
3
4
5
6
7
8
package main

import "chorder.net/example"

func main(){
example.Src1()
example.Src2()
}

执行结果:

1
2
3
4
5
6
>go run packge_test.go

chorder.net/example/src1.go init() has been called.
chorder.net/example/src2.go init() has been called.
chorder.net/example/src1.go Src1() has been called.
chorder.net/example/src2.go Src1() has been called.

1 概述

在典型的IPv4网络下,由于NAT技术的广泛使用,端口映射是一种刚需。(下文所涉及“端口转发”、“端口映射”均指同一种技术。)

经典的端口映射方法例如iptables端口转发、SSH端口转发、webshell流量转发(regeorg)、socks代理等技术,是比较常见的用于建立内网通道的技术。本文分享一种利用内网网关设备开放的upnp协议进行端口映射的方法。

关于upnp协议的解释,参考百度百科-UPNP
upnp协议的使用场景很多,端口转发是upnp其中一个功能,但是想要使用该功能,需要网关设备的支持。

2 场景

使用upnp进行端口转发的场景,以下图网络拓扑为例:

内网服务器Server:内网地址192.168.1.2,开放端口1234
网关设备Gateway:内网地址192.168.1.1,公网地址x.x.x.x

常规情况下,如果需要通过公网地址x.x.x.x访问192.168.1.2中的开放端口1234,则可以在Gateway中配置端口转发,但此时至少要有192.168.1.1的(部分)权限。

如果Gateway具备unpn功能,并且启用了unpn服务,则可以在Gateway外部通过upnp指令,打开Gateway上的转发通道,从而完成端口转发。

Gateway开启upnp示例(一般位于路由等设备的配置界面中):

3.利用

metasploit

Metasplioit中有upnp相关的插件,但是不太好用,姿势问题?
插件是:

  • auxiliary/scanner/upnp/ssdp_msearch
  • auxiliary/admin/upnp/soap_portmapping

可以自行探索之

miranda-upnp

miranda-upnp是之前在Github上找到的一个工具,基于python2编写,可以用于内网upnp端口转发通道的建立。

miranda-upnp(github)

upnp协议

也可以根据upnp协议自行实现相关工具,协议使用可以参考这篇文章

4. 演示

以第2节中的拓扑为例,在内网IP为192.168.1.2的Server中运行miranda-upnp,打开转发通道,在Gateway的x.x.x.x:1234与Server 192.168.1.2:1234之间建立映射。

运行miranda-upnp后,首先执行msearch

1
2
3
4
5
6
7
8
9
10
11
12
upnp> msearch

Entering discovery mode for 'upnp:rootdevice', Ctl+C to stop...

****************************************************************
SSDP reply message from 192.168.1.1:49652
XML file is located at http://192.168.1.1:49652/49652gatedesc.xml
Device is running Linux/3.10.53-HULK2, UPnP/1.0, Portable SDK for UPnP devices/1.6.25
****************************************************************

^C
Discover mode halted...

此时已经获得Gateway中的SSDP描述文件,Ctrl-C停止SSDP搜寻。

执行host list,列出扫描到的主机,并执行host get 0选中主机0(Gateway):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
upnp> host list

[0] 192.168.1.1:49652

upnp> host get 0

Requesting device and service info for 192.168.1.1:49652 (this could take a few seconds)...

Host data enumeration complete!

upnp> host info 0 deviceList

InternetGatewayDevice : {}
WANDevice : {}
WANConnectionDevice : {}

执行deviceListservices命令分别查看设备列表和设备中的服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
upnp> host info 0 deviceList InternetGatewayDevice services

Layer3Forwarding : {}

upnp> host info 0 deviceList WANDevice services

WANCommonInterfaceConfig : {}

upnp> host info 0 deviceList WANConnectionDevice services

WANIPConnection : {}

upnp> host info 0 deviceList WANConnectionDevice services

WANIPConnection : {}

upnp>

常规的端口转发功能我们要使用的是WANConnectionDevice设备的WANIPConnection服务,执行actions查看其动作,这里可以看到存在一个名为AddPortMapping的action,我们只要调用这个action,并填写相应参数,即可完成端口转发。

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
upnp> host info 0 deviceList WANConnectionDevice services WANIPConnection actions

AddPortMapping : {}
GetNATRSIPStatus : {}
GetGenericPortMappingEntry : {}
GetSpecificPortMappingEntry : {}
ForceTermination : {}
GetExternalIPAddress : {}
GetConnectionTypeInfo : {}
GetStatusInfo : {}
SetConnectionType : {}
DeletePortMapping : {}
RequestConnection : {}

upnp> host send 0 WANConnectionDevice WANIPConnection AddPortMapping

Required argument:
Argument Name: NewPortMappingDescription
Data Type: string
Allowed Values: []
Set NewPortMappingDescription value to: XXXXX

Required argument:
Argument Name: NewLeaseDuration
Data Type: ui4
Allowed Values: []
Set NewLeaseDuration value to: 36000

Required argument:
Argument Name: NewInternalClient
Data Type: string
Allowed Values: []
Set NewInternalClient value to: 192.168.1.2

Required argument:
Argument Name: NewEnabled
Data Type: boolean
Allowed Values: []
Set NewEnabled value to: 1

Required argument:
Argument Name: NewExternalPort
Data Type: ui2
Allowed Values: []
Set NewExternalPort value to: 1234

Required argument:
Argument Name: NewRemoteHost
Data Type: string
Allowed Values: []
Set NewRemoteHost value to: 0.0.0.0

Required argument:
Argument Name: NewProtocol
Data Type: string
Allowed Values: ['TCP', 'UDP']
Set NewProtocol value to: TCP

Required argument:
Argument Name: NewInternalPort
Data Type: ui2
Allowed Values: []
Set NewInternalPort value to: 1234

可以看到此时Gateway中已经激活了相应的转发通道:

在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演示程序
编写一个单进程非阻塞多客户的套接字客户端

Your browser is out-of-date!

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

×