RSS

Category Archives: Technology

20 Line Sudoku Solver

Having a discussion with some friends one day about the easiest way to verify whether or not a completed 9×9 Sudoku puzzle is valid, meaning that all rows, columns and 3×3 sub-grids contain all digits from 1 to 9, I began formulating a way to solve these puzzles using a computer. Within that same week I inevitably sat down and began coding it out.

Certain that this very program had been written by CS students the world over, I wanted to add something else to this project to make it just a little more difficult. So, motivated by the IOCCC, I decided to try my hand at obfuscated code and solve the problem in as few lines as possible.

The algorithm I developed was simple. Starting at tile x, a list x is generated containing all of the possible values tile x could assume without interfering with its relevant non-empty neighbors, where neighbors refers to tiles in the same row, column and sub-grid as tile x. If tile x has a predefined value that must be solved around, that is tile x‘s only valid value. Tile x then assumes a value, the first and lowest numerical value contained in list x or its predefined value. The process is then repeated for tile x+1.

This is done for all 81 tiles (0 to 80), 0 representing the top-left tile and 80 representing the bottom-right tile. Once list x has been generated for tile x and tile x has assumed the first value in that list, the program recursively calls for the generation of list x+1 and the assignment of tile x+1‘s value.

If no values can be generated for arbitrary tile x then then tile x-1 moves on to assuming the next value in list x-1. And we try again generating list x for tile x. If, however, list x-1 were to run out of values for tile x-1 to assume, tile x-2 would assume the next value in list y-2. This is the recursive nature of the algorithm. Whenever arbitrary list x runs out of possible values that tile x could assume, or is unable to generate any valid values, that means that the list of values that the program has assumed to be correct so far (ie tile 0 to x) is not going to work and we must go back to the last tile that has any remaining values in its list and start from there.

If we run out of values in list 0, this means that the Sudoku puzzle is unsolvable. On the other hand, if a valid list x is generated for tile 80, this means that the combination of assumed values for all 81 tiles play nicely and we have found a solution for our Sudoku Puzzle.

So what did I come up with? 20 lines of mumbo jumbo c that accepts a Sudoku puzzle in the form of a 9×9 grid and spits out the first solution it comes up with

int g[81][11];

int tile(int x)
{
	int a;
	for(a=1; a<10 && x<81 && !g[x][10]; a++)
		g[x][a] = 1;

	for(a=0; a<9 && x<81 && !g[x][10]; a++)
		g[x][g[x/9*9+a][0]] = g[x][g[x%9+9*a][0]] = g[x][g[x%9/3*3+x/27*27+a/3*9+a%3][0]] = 0;

	for(a=0; a<10 && x<81; a++)
		if(g[x][a])
		{
			g[x][0] = a?a:g[x][10];

			if(tile(x+1))
				return printf(x%9?"%d ":"%d \n",g[x][0])|1;
		}

	g[x][0] = g[x][10];
	return x>80;
}

int main()
{
	int x;
	char c[9];
	for(x=0; x<81 && (x%9 || scanf("%s",c)|1); x++)
		g[80-x][10] = g[80-x][0] = (c[x%9]-48);

	return tile(0);
}

The code, being obfuscated and all, is pretty hard to follow. Some people make a sport out of understanding obfuscated code completely out of context. It’s definitely a great way to excerpter your code comprehension skills and I definitely recommend trying to figure out exactly what the heck is going on inside this little snippet of code by yourself. But if you don’t quite feel like fumbling through some high schooler’s code, don’t fret. I will be dissecting my code line by line and explaining what it all does.

Before we dive into the main method. let’s take a look at the top line.

int g[81][11];

This is essentially our Sudoku puzzle. It holds a list of 11 numbers for each of the 81 Sudoku tiles, and is a more literal example of the tile xand list x constructs I was referencing in the algorithm explanation. So how exactly is this data organized? It’s simple actually. It contains the only 11 numbers I need to know in order to solve any Sudoku puzzle for each of the 81 tiles. These numbers are:

g[x][ 0] – the current value of tile x assumes.
g[x][ 1] through g[x][9] – a boolean value representing whether or not this index is a valid value which tile x can assume.
g[x][10] – the value that tile x must assume. ie if you are solving a puzzle where the top-left most tile must be a 6: g[0][10] = 6. If the top-left most tile was blank and the current value must be solved for: g[0][10] = 0.
Now lets head over to the main method and start exactly where the computer starts.

int x;
char c[9];
for(x=0; x<81 && (x%9 || scanf("%s",c)|1); x++)
	g[80-x][10] = g[80-x][0] = (c[x%9]-48);

This is the loop that reads the puzzle in from the console and stores the appropriate data in our g[][] structure. scanf("%s",c) is called every time y is an even multiple of 9, so the buffer c[] is filled at the beginning of every new line, and then 9 iterations are spent copying the data from the newly filled buffer c[] over to the appropriate position ing[][].

(c[x%9]-48) takes the character value and returns the appropriate integer value. With 48 being the ASCII value for '0' simply subtracting 48 does this rather easily.

g[80-x][10] sets the 10th number in list x to the integer value returned by (c[x%9]-48). Remember, g[x][10] holds tile x‘s predefined value, with a value of 0 meaning tile x must be solved for.

g[80-x][0] sets the first value in list x to the integer value returned by (c[x%9]-48). Remember, g[x][0] contains the current value that tile x is assuming. If tile x has a predefined value, then that is the only value that it can ever assume, so it is set right off the bat. However, if g[x][10] contains a value of 0, meaning that tile x‘s value must be solved for, then then current value stored int g[x][0]doesn’t’t matter and will be altered shortly.But what is with the whole [80-x] thing? Well, the for loop is essentially reading the input grid from top-left to bottom-right and storing the values linearly into our g[][] structure. Except the [80-x] is fooling with out indexes and inserting these values into our g[][] structure backwards. Meaning the top-left value in our grid is now position 80 and the bottom-left value is our starting index, 0. But why are we doing this? Remember, the algorithm is recursive, as it solves the puzzle, it is crawling deeper and deeper inside of its self, building up a stack trace. Once a solution is found, it starts popping off method calls and returning to the root method. As it is doing this, it stops real quick to print out the value that it has found to be correct for that method call (read: tile). So what does all this mean? It means that it prints out the solved grid backwards! So, creating the grid backwards means it will print out the solution exactly how we want it.

I would like to point out that this could have been simplified had I chosen to accept input as an argument, but I wanted this program to actually be somewhat functional. Who wants to type in an 81 character Sudoku puzzle all in one line?

return tile(0);

This is the method call that starts solving our puzzle. It is a recursive function and i’m telling it to start by solving tile 0, the now the bottom-left corner. The only reason I am returning its value back to the operating system it to make my compiler stop whining about how main() isn’t returning a value.

 int tile(int x)

Okay, let’s dive into the magical method tile(). This is the method that does the solving and exactly how it accomplishes this is pretty straight forward. Upon entry, a list for tile x is generated of all the possible values that tile x could assume without interfering with any of its neighbors values. It then assumes that first value in that list. If tile x has a predefined value, no list is generated and its assumed value is still set as its predefined value from when g[][] was generated earlier. After assuming a value (or keeping its assigned value as it’s assumed value) it calls itself, this time solving the next tile in the puzzle.

tile() returns a boolean value. 1 for success, 0 meaning it was unable to assume a value. Each time tile() calls itself attempting to solve for the next tile it checks weather or not the next call was successful or not. The only way for a tile() to return successful is if it was able to assume a value for tile 80, meaning it found a combination of numbers that solves the puzzle all the way to the last tile. Once a value for tile 80 is assumed. the tile() method call responsible prints out its value and returns successful. method call 79 (who called method call 80) sees that method call 80 has found the last puzzle piece, prints out its value and returns successful. Method call 78 sees that method call 79 sees that method call 80 found the last puzzle piece and prints out its value and returns successful. This happens all the way up the stack, until it reaches call 0 which returns successful and the program exits. Now do you understand why I had to put the values in backwards, it printed out starting from method call 80

So now you know what happens right as the algorithm nears completion. But there are tons (hundreds) of failures down the line of method calls. tile() can return 0 for failure, I did’t implement this just for fun. This does’t mean that the algorithm is flawed, well I guess that depends how you define ‘flawed’. The algorithm is based on trial and error, tile() returning unsuccessful just means that the program didn’t guess the correct values for all 81 tiles in a row.

So, what does it mean when arbitrary call x returns unsuccessful while trying to solve tile x? It means that there are no more values that it can assume without interfering with any of its neighbors, it ran out of values in list x or list x came up empty. Say we got down to call 6, it generates a list of values tile 6 can assume without interfering with its neighbors, assumes the first value in that list 6 and makes the next call for tile() to do the same for tile 7. Method call 7 is unable to come up with any values that tile 7 could assume without interfering with any of its neighbors, so method call 7 returns unsuccessful. Call 6, who invoked call 7, sees that call 7 returned unsuccessful and assumes the next value in list 6 and invokes call 7 again. But if call 6 ran out of numbers to assume in its list it too would return unsuccessful and then call 5 would have to assume the next value in list 5 and invoke call 6 again. This will happen all the way back up the chain of calls until it gets to a method call who has another value in its list it can assume and execution starts over from that point. The sort of back and forth motion can happen several hundred times while finding the solution for a single puzzle. However, if call 0 returns unsuccessful, that means the puzzle was unsolvable. There is no one called before call 0 to choose a different value so call 0 to try again.

Alright, lets see how tile() does all of this.

 int tile(int x)

The first thing we notice is that tile() returns a boolean like I mentioned earlier. 1 for success, 0 for failure. It takes one parameter, int x, x denoting which tile the call is going to solve for.

 int a;

This is just the declaration of the variable that is used in all three for loops inside tile(). c does’t like us declaring variables inside the first parameter of for loops.

for(a=1; a<10 && x<81 && !g[x][10]; a++)
	g[x][a] = 1;

Okay, our first for loop, with index a as mentioned earlier. Right now, only pay attention to the first conditional in the second parameter and this is simply a for loop that counts from 1 to 9. But what is it doing with these values? Well if we look at the second line we can tell that it is setting the index 1 through 9 in the list for tile x to 1. If we remember what all of the index mean we can tell that this is marking all values from 1 to 9 as possible values that tile x can assume. Alright, before I explain what the other conditionals mean, let’s take a look at the next chunk of code

for(a=0; a<9 && x<81 && !g[x][10]; a++)
	g[x][g[x/9*9+a][0]] = g[x][g[x%9+9*a][0]] = g[x][g[x%9/3*3+x/27*27+a/3*9+a%3][0]] = 0;

Again, only paying attention to the first conditional in the second parameter of the for loop, this is just a loop counting from 1 to 9 with an index of a. So if we look at the second line, which is being iterated, this is where everything happens. The previous loop went through and marked ALL values for tile x as possible values which can be assumed. This loop goes through and marks off all of the values which cannot actually be assumed with a 0. So after this loop, the values left marked with a value of 1 in list x can be assumed by tile x. If we break the bottom line up into three lines it becomes a little less intimidating.

g[x][g[x/9*9+a][0]] = 0;
g[x][g[x%9+9*a][0]] = 0;
g[x][g[x%9/3*3+x/27*27+a/3*9+a%3][0]] = 0;

Okay, so this is the same code as before, just broken up in to three lines. The first line is responsible for marking off all values on the same row as tile x that are already being assumed by other tiles as unassumable. The second line does the same except for tiles on the same column as tile x. The third line does this for the 9 tiles inside tile x‘s 3×3 sub-grid and is a little bit more complicated.

I’m not going dive into exactly how the code does what it does, but I will explain the algorithm. Lets just focus on the first line, who marks off all assumed values on the same row as tile x as unassumable. Every tile on this row (or the whole grid for that matter) has a value ranging from 0 to 9, 0 meaning that the tiles final value must still be assumed. As we are iterating through all 9 tiles in tile x‘s row we grab each tile’s currently assumed value and mark that index in list x as 0 (unassumable). So obviously if there is a tile with a value of 5 on our row we cannot use that value, so we mark index 5 in list x as unassumable. But what about tiles with a value of 0 (undetermined)? well, they have no affect on us (or anyone else) because they are not actually assuming any value yet. So, we come across a tile with a value of 0 (undetermined), our code is going to mark index 0 in list x as 0 (unusable). Well, this just marks tile x‘s currently assuming value as 0, marking tile x as unassumed, which it already is.

Okay remember how I said to ignore the last two conditions in the second parameter of our for loops? Well it’s time to take a look at what they actually do. x<81 && !g[x][10], these two conditionals are essentially like putting the for loops inside an if statements because these values are never affected by the value of a. These two for loops and their combined process of determining what values tile x can assume only need to run if x<81, we are not at or past tile 81, meaning we are off the gird and !g[x][10], list x index 10 has a value of 0 meaning that tile x‘s value is not predetermined.

for(a=0; a<10 && x<81; a++)
	if(g[x][a])
	{
		g[x][0] = a?a:g[x][10];

		if(tile(x+1))
			return printf(x%9?"%d ":"%d \n",g[x][0])|1;
	}

Now that we know what values we can assume, its time to start assuming them one by one in an effort to find one that works.

for(a=0; a<10 && x<81; a++)

Again using a as our index we have a for loop that counts from 0 to 9. Note the second conditional in our for loop, this loop also only runs if we are on the grid (tile 80 or less).

g[x][0] = a?a:g[x][10];

if( tile( x+1 ) )
	return printf(x%9?"%d ":"%d \n",g[x][0]) | 1;

This code from inside the if statement is execute for all values of a 0 through 9 where list xindex a is non zero.

g[x][0] = a?a:g[x][10];

This sets g[p][0], the value tile x is currently assuming. If a equals 0, meaning index 0 for list x is be non zero, meaning that tile x has a predefined value that is MUST assume. If tile x has a predefined value then a list of possible values to assume was never generated and this code will only be run once since. If, however, a has a value greater than zero, meaning that tile x has no predefined value and a is a value that can be assumed by tile x, we set a as the value that tile x is currently assuming

if( tile( x+1 ) )
	return printf(x%9?"%d ":"%d \n",g[x][0]) | 1;

Now that we have an assumed value it’s time to try it out by solving for the next tile and seeing if it returns successful or not. We do this by calling tile( x+1 ) inside of an if statement. Note the x+1 in tile( x+1 ) is calling for the solution of the next tile. If this call returns successful that means that all the way down the line of calls, call 80 found a solution for the last tile and we are now shooting back up the chain of calls (stack) and each tile prints out their assumed value and returns successful letting its parent call know to do the same.

 return printf(x%9?"%d ":"%d \n",g[x][0]) | 1;

This is the code executed when the child call returns successful. It’s basically a fancy way of printing out the assumed value and jumping to a new line if the tile is a multiple of 9, creating a 9×9 grid. The | 1 on the end is to ensure that printf(), a void method, return a value of 1 representing a successful execution.

So what happens when the tile( x+1 ) child call returns unsuccessful? we keep on iterating through out list of values, assume the next one and call tile( x+1 ) again. If we run out of values to assume on our list we exit the for loop and jump to this bit of code

g[x][0] = g[x][10];
return x>80;

This block of code will execute under two, non mutually exclusive, conditions. If and arbitrary method call to tile() runs out of values to assume we will first reset its assumed value to its original using the first line. If we have made it to the end of tile()‘s method body and x has a value less than or equal to 80 that means we haven’t found a value for tile 80 yet and therefore must make our way back up the chain of method calls and try again from that first tile that can assume another value. Remember how the last three for loops had the x<81 conditional that acted like an if statement? Well this is where that comes into play. If tile() was executed with a value of 81, meaning that tile 80 found the last puzzle piece and the executed for the solution of piece 81. tile() is smart enough to know that it is only solving for 81 pieces and doesn’t execute any code when told to solve for a piece after piece 80. So it jumps right to the end and returns successful. It realizes that if you are calling for the solution of piece 81 that you have found all of the pieces and therefore have completed the puzzle. This sets off the chain reaction that shoots up the chain of method calls and causes everybody to print out their assumed value.

Now that you hopefully understand how everything works, it time to have a little fun. Here are three of the worlds harders Sudoku puzzles for you to try out, the code should solve each of them in a fraction of a second.

100007090	850002400	005300000
030020008	720000009	800000020
009600500	004000000	070010500
005300900	000107002	400005300
010080002	305000900	010070006
600004000	040000000	003200080
300000010	000080070	060500009
040000007	017000000	004000030
007000300	000036040	000009700

http://reidhoruff.com/request.php?url=obfuscated_sudoku
 
Bình luận về bài viết này

Posted by trên Tháng Mười Một 21, 2011 in Technology

 

10 Examples of HotSpot JVM Options in Java

JVM parameters in Java

There are hundreds of JVM parameters or JVM Options exists inside sun jdk and its virtually impossible to keep track of every single jvm option and based on my experience we don’t even use most of JVM flags except couple of important jvm option related to java heap size, java options for printing garbage collection details and most likely JVM switches for setting up remote debugging in Java. but there are many other useful category of JVM parameters which you at least like to be familiar even if not intending to use it more frequently. In this article we will see examples of 10 different categories of JVM parameter which I found useful and use more frequently than other. I would recommend to get a full knowledge of what does a particular JVM options does by referring official list of JVM options.
On the basis of how we specify JVM option it can be divided into two parts, JVM Options which starts with –X and those which starts with -XX:
1)    JVM Options that begin with -X are non-standard (thy are not guaranteed to be supported on all JVM implementations), and are subject to change without notice in subsequent releases of the JDK.
2)    JVM Options or parameters which are specified with -XX are not stable and are not recommended for casual use. These options are subject to change without notice also.
I was thinking about writing post on JVM options when I completed my post on Java Heap Size and Java Garbage Collection because these are two main area where we see usages of various JVM flags. But it didn’t happened even after I covered OutOfMemoryError post which has some JVM option to solve OutOfMemoryError in Java. Now I am happy that I have completed this piece of information and its ready to be published. As always I look for your feedback, suggestions and any other JVM flags which I have missed and you guys find useful to share.

Good knowledge of JVM options specially related to GC tuning is important for time critical application e.g. high volume low latency electronic trading platform where every micro seconds matter. though getting right combination requires lot of profiling and trial and error and depends heavily on nature of trading application.

Important Points about JVM Options:

1)    Boolean JVM options can be  turned on with -XX:+ and can be turned off with -XX:-.
2)    Numeric JVM Options can be set with -XX:=. Numbers can include ‘m’ or ‘M’ for megabytes, ‘k’ or ‘K’ for kilobytes, and ‘g’ or ‘G’ for gigabytes (for example, 32k is the same as 32768).
3)    String JVM options can be set by using -XX:=, and usually used to specify a file, a path, or a list of commands.
The command java -help lists the standard options (standard across different JVM implementations) for the Java application launcher. The command java -X can be used to see the Java application launcher’s non-standard (X for extension specific to that JVM) arguments.The -X options are non-standard and subject to change without notice. If you wish to detect which JVM arguments your currently running Java application is using, you can use the ManagementFactory.getRuntimeMXBean().getInputArguments()
Now here is my list of important JVM flags, switches, options or parameters which is most commonly used while running Java applications:
1) JVM memory options related to java heap size
Following three JVM options are used to specify initial and max heap size and thread stack size while running Java programs.
 -Xms        set initial Java heap size
 -Xmx        set maximum Java heap size
 -Xss>         set java thread stack size
2) JVM option to print gc details
-verbose:gc logs garbage collector runs and how long they’re taking. I generally use this as my first tool to investigate if GC is a bottleneck for a given application.
-XX:+PrintGCDetails includes the data from -verbose:gc but also adds information about the size of the new generation and more accurate timings.
-XX:-PrintGCTimeStamps  Print timestamps at garbage collection.
3) JVM parameters to specify Java Garbage collector
-XX:+UseParallelGC      Use parallel garbage collection for scavenges
-XX:-UseConcMarkSweepGC Use concurrent mark-sweep collection for the old generation. (Introduced in 1.4.1)
-XX:-UseSerialGC        Use serial garbage collection. (Introduced in 5.0.)

beware when you use GC Parameters if you are working on time critical application e.g. high frequency trading application. As  GC is time consuming operation and its desired to create a balance.


4) JVM debug options JVM options for remote debugging
-Xdebug -Xnoagent -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000
to read more about remote debugging check How to Setup Java remote debugging in Eclipse and 10 Java debugging tips in Eclipse 
5) JVM options related to profiling
-Xprof
-Xrunhprof
6) JVM options related to java classpath
Xbootclasspath specifies classpath entries you want loaded without verification. The JVM verifies all classes it loads to ensure they don’t try to dereference an object with an int, pop extra entries off the stack or push too many, and so on. This verification is part of the reason why the JVM is very stable, but it’s also rather costly, and responsible for a large part of start up delay. Putting classes on the bootclasspath skips this cost, but should only be used when you know the classes have been verified many times before. In JRuby, this reduced startup time by half or more for a simple script. The –Xbootclasspath option can be used to either prepend (/p) or append (/a) resources to the bootstrap classpath. You Can read more about Java Classpath in my articles How Classpath Works in Java and How to Solve ClassNotFoundException in Java
7) JVM options to change  Perm Gen Size
These JVM optiosn are quite useful to solve java.lang.OutOfMemoryError:Perm Gen Space.
-XX:PermSize and MaxPermSize
-XX:NewRatio=2  Ratio of new/old generation sizes.
-XX:MaxPermSize=64m     Size of the Permanent Generation.
8) JVM parameters to trace classloading and unloading
-XX:+TraceClassLoading and -XX:+TraceClassUnloading are two JVM options which we use to print logging information whenever classes loads into JVM or unloads from JVM. These JVM flags are extremely useful if you have any memory leak related to classloader and or suspecting that classes are not unloading or garbage collected.
9) JVM switches related to logging
-XX:+TraceClassLoading and -XX:+TraceClassUnloading print information class loads and unloads. Useful for investigating if you have a class leak or if old classes (like JITed Ruby methods in JRuby) are getting collected or not. You can read more about logging in Java on my post 10 Tips while logging in Java
-XX:+PrintCompilation prints out the name of each Java method Hotspot decides to JIT compile. The list will usually show a bunch of core Java class methods initially, and then turn to methods in your application. In JRuby, it eventually starts to show Ruby methods as well
10) JVM Switches for debugging purpose
-XX:HeapDumpPath=./java_pid.hprof  Path to directory or file name for heap dump.
-XX:-PrintConcurrentLocks       Print java.util.concurrent locks in Ctrl-Break thread dump.
-XX:-PrintCommandLineFlags   Print flags that appeared on the command line.
That’s all on JVM Options, I understand its not possible to remember all JVM flags but at-least having an idea of what kind of JVM flags are available is good asset. For full list of JVM options you can refer these link from Oracle Java site: Java Hotspot VM Options

http://javarevisited.blogspot.com/2011/11/hotspot-jvm-options-java-examples.html

 
1 bình luận

Posted by trên Tháng Mười Một 15, 2011 in Technology

 

DESIGN PATTERN

Trích bài “Design Pattern – Thiết kế theo mô hình mẫu” – Phạm Đình Trường-Software Engineer, GrapeCity Inc
Trong phát triển phần mềm hiện đại, kiến trúc tổng thể của dự án đóng một vai trò quan trọng, đặc biệt với bộ khung (framework) và mẫu thiết kế (design pattern). Bài viết này sẽ giúp các bạn hiểu được một cách tổng quan về pattern cũng như cách thức thiết kế một số pattern tiêu biểu.

PATTERN là gì?

Pattern mô tả một giải pháp chung đối với một vấn đề nào đó trong thiết kế thường được “lặp lại” trong nhiều dự án. Nói một cách khác, một pattern có thể được xem như một “khuôn mẫu” có sẵn áp dụng được cho nhiều tình huống khác nhau để giải quyết một vấn đề cụ thể. Trong bất kỳ hệ thống phần mềm hướng đối tượng nào chúng ta cũng có thể bắt gặp các vấn đề lặp lại.

Đặc điểm chung:

• Pattern được hiểu theo nghĩa tái sử dụng ý tưởng hơn là mã lệnh. Pattern cho phép các nhà thiết kế có thể cùng ngồi lại với nhau và cùng giải quyết một vấn đề nào đó mà không phải mất nhiều thời gian tranh cãi. Trong rất nhiều trường hợp, dự án phần mềm thất bại là do các nhà phát triển không có được sự hiểu biết chung trong các vấn đề về kiến trúc phần mềm. Ngoài ra, pattern cũng cung cấp những thuật ngữ và khái niệm chung trong thiết kế. Nói một cách đơn giản, khi đề cập đến một pattern nào đấy, bất kỳ ai biết pattern đó đều có thể nhanh chóng hình dung ra “bức tranh” của giải pháp. Và cuối cùng, nếu áp dụng pattern hiệu quả thì việc bảo trì phần mềm cũng được tiến hành thuận lợi hơn, nắm bắt kiến trúc hệ thống nhanh hơn.

• Pattern hỗ trợ tái sử dụng kiến trúc và mô hình thiết kế phần mềm theo quy mô lớn. Cần phân biệt design pattern với framework. Framework hỗ trợ tái sử dụng mô hình thiết kế và mã nguồn ở mức chi tiết hơn. Trong khi đó, design pattern được vận dụng ở mức tổng quát hơn, giúp các nhà phát triển hình dung và ghi nhận các cấu trúc tĩnh và động cũng như quan hệ tương tác giữa các giải pháp trong quá trình thiết kế ứng dụng đối với một chuyên khu riêng biệt.

• Pattern đa tương thích. Pattern không phụ thuộc vào ngôn ngữ lập trình, công nghệ hoặc các nền tảng lớn như J2EE của Sun hay Microsoft .NET Framework.

Tiềm năng ứng dụng của pattern là rất lớn. Các thiết kế dựa trên pattern được sử dụng khá nhiều ở các phần mềm mã nguồn mở, trong nền tảng J2EE hoặc .NET… Trong các dạng ứng dụng này, có thể dễ dàng nhận ra một số tên lớp chứa các tiền tố hoặc hậu tố như Factory, Proxy, Adapter…

PHÂN LOẠI PATTERN

Pattern được phân loại ra làm 3 nhóm chính sau đây:

• Nhóm cấu thành (Creational Pattern): Gồm Factory, Abstract Factory, Singleton, Prototype, Builder… Liên quan đến quá trình khởi tạo đối tượng cụ thể từ một định nghĩa trừu tượng (abstract class, interface).

• Nhóm cấu trúc tĩnh (Structural Pattern): Gồm Proxy, Adapter, Wrapper, Bridge, Facade, Flyweight, Visitor… Liên quan đến vấn đề làm thế nào để các lớp và đối tượng kết hợp với nhau tạo thành các cấu trúc lớn hơn.

• Nhóm tương tác động (Behavioral Pattern): Gồm Observer, State, Command, Iterator… Mô tả cách thức để các lớp hoặc đối tượng có thể giao tiếp với nhau.

Dưới đây chúng ta sẽ tìm hiểu chi tiết một số pattern tiêu biểu nhất: Factory, Abstract Factory, Singleton, Proxy, Adapter và Wrapper. Chúng ta quy ước với nhau rằng “giao diện lớp” được hiểu như interface hoặc abstract class vì đây đơn thuần là các định nghĩa lớp.

FACTORY PATTERN

Định nghĩa

Factory Pattern định nghĩa một lớp (interface, abstract, class) đóng vai trò như một “nhà xưởng” có nhiệm vụ khởi tạo đối tượng “cụ thể” khi ứng dụng chạy. Tại thời điểm thiết kế đối tượng này được định nghĩa trừu tượng.

Đặc điểm

Kiểm soát được các hoạt động trong suốt chu kỳ sống của đối tượng, như khởi tạo đối tượng, huỷ đối tượng… Đảm bảo cho các đối tượng được thực thi an toàn. Nắm được thông tin về những đối tượng nào được tạo ra và được khởi tạo ra sao. Nói cách khác, các đối tượng được quản lý tốt hơn và an toàn hơn với Factory Pattern.

Đối tượng Factory thường được đặt tên theo những chuẩn khác nhau nhưng vẫn có thể dễ dàng nhận ra thiết kế Factory Pattern ẩn chứa trong đó. Ví dụ: BankFactory,…

Tính chất đóng gói (encapsulation) thể hiện rõ trong Factory Pattern; các thông tin liên quan đến truy cập đối tượng được che giấu trong Factory. Thiết kế Factory luôn có một thủ tục khởi tạo đối tượng, ví dụ createObject().

Factory Pattern tuân thủ nguyên tắc thiết kế DIP (Dependency Inversion Principle): không nên phụ thuộc vào những thứ quá cụ thể.

Phân loại

Factory Pattern được thiết kế theo một trong hai cách sau đây:

Based-class Factory Pattern: Mẫu này sử dụng tính chất thừa kế để phân loại các đối tượng được tạo ra.

Based-object Factory Pattern: Sử dụng mối quan hệ kết hợp để tham chiếu tới một đối tượng sẽ được tạo ra. Đối tượng được tạo ra sẽ trở thành một phần hay thuộc tính của lớp Factory. Chúng ta thường hay gặp loại này trong Abstract Factory Pattern được trình bày ở phần tiếp theo.

ABSTRACT FACTORY PATTERN

Định nghĩa

Abstract Factory cung cấp một giao diện lớp có chức năng tạo ra một tập hợp các đối tượng liên quan hoặc phụ thuộc lẫn nhau mà không chỉ ra đó là những lớp cụ thể nào tại thời điểm thiết kế.

Về bản chất, Abstract Factory Pattern chỉ khác Factory Pattern ở chỗ bản thân đối tượng Factory không được chỉ ra cụ thể tại thời điểm thiết kế, tức nó là một giao diện hoặc lớp trừu tượng (interface, abstract). Nếu như Factory Patttern phân loại đối tượng dựa trên tham số đầu vào thì đối với Abstract Factory Pattern, thủ tục createObject() còn phụ thuộc thêm vào các yếu tố phụ khác như môi trường hệ điều hành chẳng hạn. Ứng với mỗi yếu tố phụ thứ hai ta có một lớp Factory cụ thể.

Thiết kế động với Abstract Factory
Một trong những vấn đề gặp phải là khung giao diện Abstract Factory thường hay bị sửa đổi, thí dụ như bổ sung thủ tục chẳng hạn, khi đó các lớp cụ thể thực thi giao diện này sẽ phải được dịch và triển khai lại. Để giảm nhẹ vấn đề này người ta thường thiết kế giao diện Abstract Factory một cách linh động.

SINGLETON PATTERN
(Static Factory Pattern)

Định nghĩa

Singleton Pattern đảm bảo một lớp chỉ có một thực thể (instance) duy nhất được tạo ra và đồng thời cung cấp một truy cập toàn cục đến đối tượng được tạo ra.
Chúng ta xét trường hợp có nhiều đối tượng có cùng chung một số tính chất nào đó được tạo ra ứng với mỗi một yêu cầu từ các đối tượng khách (client), lúc này độ phức tạp sẽ tăng lên và ứng dụng sẽ chiếm dụng nhiều vùng nhớ hơn. Singleton Pattern là một giải pháp đặc biệt của Factory Pattern ở chỗ đối tượng sinh ra là điểm truy cập toàn cục “duy nhất” đối với mọi chương trình gọi đến, hay nói một cách khác tất cả các đối tượng khách gọi đến đều chia sẻ đối tượng được tạo ra.

Ứng dụng rõ rệt nhất của Singleton Pattern có thể thấy trong dịch vụ web khi triệu gọi các đối tượng từ xa, ở đó đối tượng nằm trên server hoặc sẽ phục vụ chung cho tất cả các ứng dụng khách (singleton) hoặc sẽ chỉ đáp ứng một ứng dụng khách riêng lẻ nào đó rồi tự bị phá huỷ sau đó (single call).

Về các mẫu thiết kế tiêu biểu trong nhóm cấu thành: Factory, Abstract Factory và Singleton, các bạn có thể tham khảo thêm tài liệu về phương pháp xây dựng cụ thể cũng như mã nguồn chương trình viết bằng C#.NET tại địa chỉ:

http://www.codeproject.com/gen/desig…assFactory.asp

PROXY PATTERN

Định nghĩa

Proxy Pattern là mẫu thiết kế mà ở đó tất cả các truy cập trực tiếp một đối tượng nào đó sẽ được chuyển hướng vào một đối tượng trung gian (Proxy Class).
Nếu như Factory Pattern giúp quản lý đối tượng tốt hơn thì Proxy Pattern lại có nhiệm vụ bảo vệ việc truy cập một đối tượng thông qua Proxy, hay còn gọi là truy cập gián tiếp. Proxy được ủy quyền về phía ứng dụng khách cho phép tương tác với đối tượng đích theo những cách khác nhau; như gửi yêu cầu một dịch vụ nào đó, theo dõi trạng thái và vòng đời đối tượng, xây dựng lớp vỏ bảo vệ đối tượng… Thí dụ chúng ta phát hiện ra một đối tượng trong một thư viện DLL có thể bị khai thác truy cập vào một số trường quan trọng, khi đó chúng ta không thể mở mã nguồn thư viện đã được dịch để vá lỗ hổng, giải pháp lúc này là xây dựng một proxy ngăn chặn truy cập các trường đó và cuối cùng biên dịch lại thành một DLL mới.

Phân loại

Độ phức tạp của giải pháp sử dụng Proxy Pattern phụ thuộc vào tình huống bài toán đưa ra, chúng ta sẽ lần lượt tìm hiểu nguyên tắc làm việc của các proxy dưới đây:
Remote Proxy: Client truy cập qua remote proxy để tham chiếu tới một đối tượng được bảo vệ nằm bên ngoài ứng dụng (trên cùng máy hoặc máy khác) như dịch vụ Windows, dịch vụ web, ứng dụng ở xa… Mô hình này “che giấu” đối tượng được triệu gọi đang nằm ở rất xa đâu đó và client có vẻ như truy cập vào đối tượng nằm trên cùng một chuyên khu làm việc (domain).

Virtual Proxy: Virtual Proxy tạo ra một đối tượng trung gian mỗi khi có yêu cầu tại thời điểm thực thi ứng dụng, nhờ đó làm tăng hiệu suất của ứng dụng.

Monitor Proxy: Monitor Proxy sẽ thiết lập các ràng buộc bảo mật trên đối tượng cần bảo vệ, ngăn không cho client truy cập một số trường quan trọng của đối tượng.

Protection Proxy: Đối với proxy này thì phạm vi truy cập của các client khác nhau sẽ khác nhau. Protection Proxy sẽ kiểm tra các quyền truy cập của client khi có một dịch vụ được yêu cầu.

Cache Proxy: Cung cấp không gian lưu trữ tạm thời cho các kết quả trả về từ đối tượng nào đó, kết quả này sẽ được tái sử dụng cho các client chia sẻ chung một yêu cầu gửi đến và do đó làm tăng đáng kể hiệu suất chương trình.

Firewall Proxy: Bảo vệ đối tượng từ chối các yêu cầu xuất xứ từ các client không tín nhiệm.

Smart Reference Proxy: Là nơi kiểm soát các hoạt động bổ sung mỗi khi đối tượng được tham chiếu, ví dụ như kiểm soát vòng đời của đối tượng, lưu lại số lần tham chiếu vào đối tượng…

Synchronization Proxy: Đảm bảo nhiều client có thể truy cập vào cùng một đối tượng mà không gây ra xung đột. Thực tế có rất nhiều tình huống khiến chúng ta phải nghĩ đến thiết kế này. Một synchronization proxy được thiết lập có thể kiểm soát được nhiều yêu cầu cập nhật dữ liệu một cách đồng thời, tại thời điểm bắt đầu cập nhật chỉ có một client với mức ưu tiên cao nhất giành được khoá để đánh dấu rằng các client khác cần phải chờ đến lượt.

Synchronization proxy hoạt động rất hiệu quả và phổ biến trong thiết kế các bài toán đa tuyến. Một hiện tượng hay xảy ra với thiết kế này là khi một client nào đó chiếm dụng khoá khá lâu (và thậm chí là mãi mãi) khiến cho số lượng các client trong danh sách hàng đợi cứ tăng lên, và do đó hoạt động của hệ thống bị ngừng trệ, có thể dẫn đến hiện tượng “tắt nghẽn” là hiện tượng khoá được giữ vô thời hạn bởi một đối tượng nào đó. Trong trường hợp này người ta cải tiến thành mẫu thiết kế phức tạp hơn, đó là Copy-On-Write Proxy.

Copy-On-Write Proxy: Cope-On-Write Proxy đảm bảo rằng sẽ không có client nào phải chờ vô thời hạn. Thiết kế này rất phức tạp do đó chỉ nên ứng dụng Copy-On-Write Proxy thay thế Synchronization Proxy khi hệ thống được dự đoán sẽ thường xuyên bị ngừng trệ hoặc có hiện tượng “tắt nghẽn” xảy ra.

Đặc điểm chung

Proxy Pattern có những đặc điểm chung sau đây:

 Cung cấp mức truy cập gián tiếp vào một đối tượng.

 Tham chiếu vào đối tượng đích và chuyển tiếp các yêu cầu đến đối tượng đó.

 Cả proxy và đối tượng đích đều kế thừa hoặc thực thi chung một lớp giao diện. Mã máy dịch cho lớp giao diện thường “nhẹ” hơn các lớp cụ thể và do đó có thể giảm được thời gian tải dữ liệu giữa server và client.

ADAPTER PATTERN

Định nghĩa

Adapter Pattern biến đổi giao diện của một lớp thành một giao diện khác mà các đối tượng client có thể hiểu được. Lớp với giao diện được tạo ra đó gọi là Adapter. Nguyên tắc cơ bản của Adapter Pattern nằm ở chỗ làm thế nào để các lớp với các giao diện không tương thích có thể làm việc được với nhau.

Nguyên lý xây dựng Adapter Pattern khá đơn giản: chúng ta xây dựng một lớp với một giao diện mong muốn sao cho lớp đó giao tiếp được với một lớp cho trước ứng với một giao diện khác.

Adapter Pattern không quản lý tập trung các đối tượng gần giống nhau như Factory Pattern, mà kết nối với nhiều lớp không có liên quan gì với nhau. Ví dụ lớp A sau khi thực thi giao diện của nó và vẫn muốn bổ sung các phương thức từ một lớp B nào đó, chúng ta có thể kết nối A với B thông qua hình thức kế thừa hoặc liên kết đối tượng như một thành phần. Adapter Pattern có sự giống nhau một chút với Proxy Pattern ở chỗ nó tận dụng tối đa tính chất “uỷ quyền” (delegation); lớp Adapter sẽ kết nối với một đối tượng nào đó gọi là Adaptee và Adapter sẽ được uỷ quyền truy cập vào Adaptee, lớp Adapter đóng vai trò như một kênh trung gian để client truy cập vào một số các thành phần quan trọng của lớp Adaptee.

Đặc điểm

 Adapter Pattern hướng tập trung vào giải quyết sự tương thích giữa hai giao diện đang tồn tại, giảm công sức viết lại mã lệnh xuống mức tối thiểu có thể được.

 Tái sử dụng giao diện cũ và Adapter Pattern chỉ thực sự cần thiết khi mọi thứ đã được thiết kế từ trước.

Phạm vi ứng dụng

Adapter Pattern được ứng dụng trong các trường hợp:

 Cần tích hợp một vài module vào chương trình.

 Không thể sát nhập trực tiếp module vào chương trình (ví dụ như module thư viện đã được dịch ra .DLL, .CLASS…).

 Module đang tồn tại không có giao diện mong muốn như:

– Cần nhiều hơn phương thức cho module đó.

– Một số phương thức có thể được nạp chồng.

WRAPPER PATTERN

Wrapper Pattern là một trường hợp đặc biệt của Adapter Pattern. Nếu một Adapter chỉ đơn thuần là “nhúng” (wrap) các lớp với các giao diện không tương thích với nhau để chúng có thể hoạt động cùng nhau thì có thể được gọi bằng tên riêng Wrapper Pattern. Khi đó lớp Adapter còn được gọi là lớp Wrapper. Đây là quan hệ “có một”, tức là một giao diện không tương thích có thể được nhúng vào thành một phần của một giao diện khác.

Đặc điểm

Đối tượng Wrapper mô phỏng tất cả các hành vi (hàm, thủ tục) của giao diện được nhúng bởi các hành vi với tên y hệt. Thí dụ nếu lớp được nhúng A có thủ tục SpecificRequest() thì lớp Wrapper cũng phải có thủ tục SpecificRequest() tham chiếu đến thủ tục cùng tên của A. (Ngoài ra đối tượng Wraper có thể được bổ sung các phương thức khác nếu cần thiết). Đặc điểm này được đưa ra dựa trên nguyên tắc thiết kế “Law of Demeter” nói rằng không nên tham chiếu một đối tượng sâu hơn một lớp.

Các phương thức trong Adaptee được “nhúng” trong Wrapper bằng cách truyền lời gọi cùng với các tham số tới phương thức tương ứng trong Adaptee, và trả về kết quả giống như vậy. Các thành viên (thuộc tính, trường, sự kiện) được nhúng trong Wrapper có tính chất giống hệt như trong các lớp được nhúng (tên, kiểu dữ liệu, phạm vi truy cập…).

Từ các đặc điểm ở trên, có thể thấy rằng Wrapper Pattern cho phép một module chương trình tương tác được trong một môi trường khác biệt với môi trường phát triển của module đó (ví dụ C++ và Java).

Khác biệt giữa Wrapper Pattern và Adapter Pattern

Sự khác biệt giữa Wrapper và Adapter nằm ở mục đích sử dụng: Adapter Pattern định hướng cho một đối tượng đang tồn tại có thể làm việc được với các đối tượng khác và biến đổi logic theo một cách thức nào đó, trong khi Wrapper Pattern chỉ đơn thuần cung cấp một giao diện kết hợp các đối tượng được xây dựng từ cùng một ngôn ngữ hoặc khác ngôn ngữ, trên cùng một hệ điều hành hoặc trên những hệ điều hành khác nhau.

Proxy Adapter Pattern

Nếu một lớp Adapter đóng thêm vai trò như một proxy bảo vệ cho Adaptee thì ta có mô hình Proxy Adapter Pattern, trong trường hợp này chúng ta có thể đặt tên lớp với nghĩa kết hợp, ví dụ BankProxyAdapter.

Lời kết

Bài viết này đưa ra một số mẫu thiết kế tiêu biểu giúp các bạn thấy được tầm quan trọng của design pattern trong việc nâng cao chất lượng phần mềm ở các yếu tố: hiệu suất ứng dụng, độ ổn định, tái sử dụng, tính bảo mật… Các bạn có thể tìm hiểu thêm về các mẫu thiết kế cao cấp khác ở rất nhiều tài liệu thiết kế phần mềm.



Một UML class diagram của Factory Pattern

 
Bình luận về bài viết này

Posted by trên Tháng Mười 21, 2011 in Technology

 

Apache Thrift

Apache Thrift

by
Andrew Prunicki, Senior Software Engineer
Object Computing, Inc. (OCI)

Introduction

Thrift is a framework for creating interoperable and scalable services. Thrift was originally developed at Facebook, and contributed to Apache in order to foster greater use. Thrift is released under the Apache 2.0 license.

Through a simple and straight-forward Interface Definition Language (IDL), Thrift allows you to define and create services that are both consumable by and serviceable by numerous languages. Using code generation, Thrift creates a set of files that can then be used to create clients and/or servers. In addition to interoperability, Thrift can be very efficient through a unique serialization mechanism that is efficient in both time and space.

The choice of programming language at Facebook is based on what language is best suited for the task at hand. While pragmatic, this flexibility resulted in some difficulties when these applications needed to call one another. After analysis, Facebook’s engineers did not find anything currently existing that met their needs of interoperability, transport efficiency, and simplicity (amongst others). Out of this need, Facebook’s engineers developed efficient protocols and a services infrastructure that became Thrift. Facebook now uses Thrift for their back-end services – the reason for which it was designed.

This article is structured in the following topics:

Thrift Architecture

Thrift includes a complete stack for creating clients and servers. The diagram below depicts the Thrift stack.

Thrift Stack

The top portion of the stack is generated code from your Thrift definition file. Thrift services result in generated client and processor code. These are represented by the brown boxes in the diagram. The data structures that are sent (other than built-in types) also result in generated code. These result in the red boxes. The protocol and transport are part of the Thrift runtime library. Therefore with Thrift, you can define a service, and are free to change the protocol and transport without re-generating your code.

Thrift also includes a server infrastructure to tie the protocols and transports together. There are blocking, non-blocking, single and multithreaded servers available.

The “Underlying I/O” portion of the stack differs based on the language in question. For Java and Python network I/O, the built-in libraries are leveraged by the Thrift library, while the C++ implementation uses its own custom implementation.

Supported Protocols, Transports and Servers.

Thrift allows you to choose independently between your protocol, transport and server. With Thrift being originally developed in C++, Thrift has the greatest variation among these in the C++ implementation.

Thrift supports both text and binary protocols. The binary protocols outperform the text protocols, but there are times when the text protocols may be useful (such as in debugging). Some of the protocols Thrift supports:

  • TBinaryProtocol – A straight-forward binary format encoding numeric values as binary, rather than converting to text.
  • TCompactProtocol – Very efficient, dense encoding of data (See details below).
  • TDenseProtocol – Similar to TCompactProtocol but strips off the meta information from what is transmitted, and adds it back in at the receiver. TDenseProtocol is still experimental and not yet available in the Java implementation.
  • TJSONProtocol – Uses JSON for encoding of data.
  • TSimpleJSONProtocol – A write-only protocol using JSON. Suitable for parsing by scripting languages
  • TDebugProtocol – Uses a human-readable text format to aid in debugging.

While the above protocols describe “what” is transmitted, Thrift’s transports are the “how”. Here are a number of transports that Thrift supports:

  • TSocket – Uses blocking socket I/O for transport.
  • TFramedTransport – Sends data in frames, where each frame is preceded by a length. This transport is required when using a non-blocking server.
  • TFileTransport – This transport writes to a file. While this transport is not included with the Java implementation, it should be simple enough to implement.
  • TMemoryTransport – Uses memory for I/O. The Java implementation uses a simple ByteArrayOutputStream internally.
  • TZlibTransport – Performs compression using zlib. Used in conjunction with another transport. Not available in the Java implementation.

Lastly, Thrift provides a number of servers:

  • TSimpleServer – A single-threaded server using std blocking io. Useful for testing.
  • TThreadPoolServer – A multi-threaded server using std blocking io.
  • TNonblockingServer – A multi-threaded server using non-blocking io (Java implementation uses NIO channels). TFramedTransport must be used with this server.

Thrift allows only one service per server. Although this is certainly a limitation, this can be accommodated through a work-around. By defining a composite service that extends all of the other services that a given server should process, a single server can thus accommodate multiple services. If this work-around is insufficient for your needs, you can always create multiple servers. This scenario would mean however you would be using more resources than necessary (ports, memory, etc.).

TCompactProtocol

Given that the TCompactProtocol is the most-efficient method in the Java implement of Thrift and the example used throughout this article, some further explanation of the protocol is warranted. This protocol writes numeric tags for each piece of data. The recipient is expected to properly match these tags with the data. If the data is not present, there simply is no tag/data pair.

Compact Data Format

For integers, the TCompactProtocol performs compression using Variable-Length Quantity (VLQ) encoding from the MIDI file format. VLQ is a relatively simple format that uses 7 of 8 bits out of each byte for information, with the 8th bit used as a continuation bit. VLQ’s worst-case encoding is acceptable. For a 32bit int, it is 5 bytes. For 64 bit ints, it is 10 bytes. The diagram below shows how the decimal value 106903 (0x1A197) is represented in VLQ, saving 1 byte if it was stored in 32 bits:

Variable-Length Quantity (reference: http://upload.wikimedia.org/wikipedia/en/c/c6/Uintvar_coding.svg)

Creating A Thrift Service

Creating a Thrift service first requires creating a Thrift file describing a service, generating the code for the service, and finally writing some supporting code to start the service and client code to call it.

Definition

A Thrift definition file should be familiar to anyone who knows any language with a c syntax. Thrift files use keywords such as struct and int as well as curly braces for containing types and parenthesis for parameter lists. Thrift allows you to transfer both simple and complex types. Let’s create a service for managing courses and students.

senum PhoneType {
  "HOME",
  "WORK",
  "MOBILE"
  "OTHER"
}

struct Phone {
  1: i32    id,
  2: string number,
  3: PhoneType type
}

Here we have a Phone which contains an id, number and type. The phone number is a simple string, while the type is an enumeration limited to the values “HOME”, “WORK”, “MOBILE” and “OTHER”. Thrift uses the struct keyword to define a simple structure and senum for an enumeration. The Java generated code will create a POJO class for struct as you might expect. Disappointingly senum will not result in an Enum, but rather a simple Java String in the Phone class.

Note the numeric identifiers preceding each element in the struct. These identifiers are used during serialization/deserialization to speed parsing and minimize the size of the metadata. These numeric identifiers are what are passed over the wire rather than the string names of elements.

struct Person {
  1: i32    id,
  2: string firstName,
  3: string lastName,
  4: string email,
  5: list<Phone> phones
}

struct Course {
  1: i32    id,
  2: string number,
  3: string name,
  4: Person instructor,
  5: string roomNumber,
  6: list<Person> students
}

Here we have two more structs – Person and Course. Notice that these structs build on one another. The Person contains a list of Phones, while Course contains an instructor (one Person) and students (list of Persons). Thrift supports multiple collection types – list, set and map.

This completes the setup of our types. Let’s move on to services.

service CourseService {
  list<string> getCourseInventory(),
  Course getCourse(1:string courseNumber) throws (1: CourseNotFound cnf),
  void addCourse(1:Course course) throws (1: UnacceptableCourse uc)
  void deleteCourse(1:string courseNumber) throws (1: CourseNotFound cnf)
}

Here we have a defined a single service with 4 methods. Note that the arguments to service methods also require ordinals, just like structs. Also note that services can declare thrown exceptions and again each exception requires an ordinal:

exception CourseNotFound {
  1: string message
}

exception UnacceptableCourse {
  1: string message
}

Before moving on to code generation, Thrift supports namespaces. For each namespace, you declare the language binding, as well as the namespace. In Java, this defines the package into which the generated code is created. The namespace declaration used in the example is below:

namespace java com.ociweb.jnb.thrift.gen
namespace py com.ociweb.jnb.thrift

This completes our Thrift file.

Code Generation

Thrift supports many languages too varying degrees. The complete list is below. Be careful before assuming that just because your language has some support that it supports all of the features of Thrift. Python for instance, only supports TBinaryProtocol.

  • Cocoa
  • C++
  • C#
  • Erlang
  • Haskell
  • Java
  • OCaml
  • Perl
  • PHP
  • Python
  • Ruby
  • Smalltalk

With this being the Java News Brief, this article will focus on the Java side of Thrift. Python will also be used in order to show how Thrift does indeed support cross-language development. The sample thrift file “course.thrift” used throughout this article requires the following invocations to generate Java and Python code, respectively:

  • thrift –gen java course.thrift
  • thrift –gen py course.thrift

The Thrift code generator is written in C++. Before running the Thrift code generation tool, you will need to build Thrift from source. If you don’t care to bother for this article, simply skip this part and move on. The sample code has all you need to run the Java examples. If you decide to build it, the Thrift wiki has a page explaining this in sufficient detail (see references below).

The thrift code generator produces the following files for java and python, respectively:

|-- gen-java
|   `-- com
|       `-- ociweb
|           `-- jnb
|               `-- thrift
|                   `-- gen
|                       |-- Course.java
|                       |-- CourseNotFoundException.java
|                       |-- CourseService.java
|                       |-- Person.java
|                       |-- Phone.java
|                       `-- UnacceptableCourseException.java
`-- gen-py
    |-- __init__.py
    `-- com
        |-- __init__.py
        `-- ociweb
            |-- __init__.py
            `-- jnb
                |-- __init__.py
                `-- thrift
                    |-- CourseService-remote
                    |-- CourseService.py
                    |-- __init__.py
                    |-- constants.py
                    `-- ttypes.py

Taking a look at the Java results, an individual file was created for each Thrift struct and exception, as you might expect. The senum thrift type did not result in a Java Enum as mentioned above, however. Rather, it resulted in a simple Java String inside the Phone class with a comment in the validate method stating that this is where the values for the type should be verified. Protocol Buffers on the other hand, did produce a Java Enum for the equivalent definition (see source for details). Lastly, a CourseService.java file was generated. This file contains classes to create clients and servers.

The Python results are again what you might expect. All the Thrift struct types as well as the exceptions are in the ttypes.py module, while the client and server code is in the CourseService.py module.

Creating a Java Server

Creating a server in Thrift requires about the same amount of code as the other technologies examined in this article (REST and RMI). Check out the example code and judge for yourself. This example will use the TCompactProtocol with the FramedTransport and a non-blocking server. TFramedTransport is a requirement for using a non-blocking server, since the frames are used to determine when a given request is complete.

final TNonblockingServerSocket socket = new TNonblockingServerSocket(PORT);
final CourseService.Processor processor = new CourseService.Processor(
        new Handler());
final TServer server = new THsHaServer(processor, socket,
        new TFramedTransport.Factory(), new TCompactProtocol.Factory());

server.serve();

...

private static class Handler implements CourseService.Iface {

    @Override
    public List<String> getCourseInventory() throws
            TException {
        return db.getCourseList();
    }

    @Override
    public Course getCourse(String courseNumber) throws
            CourseNotFoundException, TException {
        final com.ociweb.jnb.thrift.db.Course dbCourse =
            db.getCourse(courseNumber);
        if (dbCourse != null) {
            return ConversionHelper.fromDbCourse(dbCourse);
        }

        return null;
    }

    @Override
    public void addCourse(Course course) throws
            UnacceptableCourseException, TException {
        com.ociweb.jnb.thrift.db.Course dbCourse =
            ConversionHelper.toDbCourse(course);
        db.addCourse(dbCourse);
    }

    @Override
    public void deleteCourse(String courseNumber) throws
            CourseNotFoundException, TException {
        if (db.getCourse(courseNumber) != null) {
            db.deleteCourse(courseNumber);
        }
    }
}

The first few lines before the ellipsis simply setup the server with the protocol, transport and server type we want to use. The handler class is where the implementation of the services is done. There is a fictitious database referenced as “db” that is used for the calls to distill the code to its relevant parts and enable re-use for the comparisons later in this article.

Creating a Java Client

Creating a Java client requires the same basic setup as the server, but does not require implementation of an interface.

//Setup the transport and protocol
final TSocket socket = new TSocket(HOST, PORT);
socket.setTimeout(SOCKET_TIMEOUT);
final TTransport transport = new TFramedTransport(socket);
final TProtocol protocol = new TCompactProtocol(transport);
final CourseService.Client client = new CourseService.Client(protocol);

//The transport must be opened before you can begin using
transport.open();

//All hooked up, start using the service
List<String> classInv = client.getCourseInventory();
System.out.println("Received " + classInv.size() + " class(es).");

client.deleteCourse("WINDOWS_301");

classInv = client.getCourseInventory();
System.out.println("Received " + classInv.size() + " class(es).");

transport.close();

The first few lines are the corollary to the setup of the server. The next several lines call the getCourseInventory and deleteCourse methods of the service. One thing to note is that while the server is using non-blocking IO, the client is using blocking IO. The equivalent non-blocking client socket was not fully implemented in the release build of Thrift that this example is built on. Each service operation actually calls send_<service-method> and recv_<service-method> method pairs internally. I tried calling these methods to see if I could get asynchronous behavior but had little luck. There is an “async” modifier that can be added to void return type methods, but the generated code looked no different with or without it. Finally, the generated receive methods don’t gracefully return if no response was received when you do call them.

Creating a Python Client

The Python client is effectively the same as the Java client except for syntax.

#Setup the transport and protocol
socket = TSocket.TSocket("localhost", 8000)
socket._timeout = 1000
transport = TTransport.TFramedTransport(socket)
protocol_factory = TBinaryProtocol.TBinaryProtocolFactory()
protocol = protocol_factory.getProtocol(transport)
client = Client(protocol)

#The transport must be opened before you can begin using
transport.open()

classInv = client.getCourseInventory()
print "Received", len(classInv), "class(es)"

client.deleteCourse("WINDOWS_301")

classInv = client.getCourseInventory()
print "Received", len(classInv), "class(es)"

Note that the Python code uses the TBinaryProtocol and not TCompactProtocol. The version of Thrift used for this article does not support TCompactProtocol for Python.

Both clients produce the following output against a newly started server:

Received 18 class(es).
Received 17 class(es).

Running Thrift

For more details on running the examples, see the README included with the source. For a quick start, simply run the following from the java directory:

  • ant start.thrift.server
  • ant run.thrift.client

Comparing Thrift

In order to validate Thrift’s value proposition, I decided to compare it with some other service technologies that are also fairly easy to use in practice. Since RESTfulwebservices seem to be popular of late, I have compared Thrift to REST. Although Protocol Buffers does not include a services infrastructure, it transports objects in a similar fashion to Thrift’s TCompactProtocol, thus making it a useful comparison. Lastly, I included RMI, since it uses a binary transport and thus can serve as a “reference implementation” of sorts for Java binary object transport.

For the comparisons, I compared the file sizes and runtime performance of each service technology. For REST, I compared both XML-based and JSON-based REST. For Thrift, I chose the most efficient transport available for Java – TCompactProtocol.

Size Comparison

To compare the sizes, I wrote out essentially the same data for each. Each write includes one Course object with 5 Person objects, and one Phone object. The definitions for each of these types can be seen in the thrift file listed above. To capture the file sizes, I used the following techniques:

Method Capture Technique
Thrift Custom client that forked the returning input stream to a file.
Protocol Buffers Stream to a file. Excludes messaging overhead.
RMI Object serialization of the response. Excludes messaging overhead.
REST Use wget from the commandline redirecting the response to a file.

The chart and table below show the results. Sizes are in bytes. None of the sizes include TCP/IP overhead.

Size Comparison Chart
Method Size* % Larger than TCompactProtocol
Thrift — TCompactProtocol 278 N/A
Thrift — TBinaryProtocol 460 65.47%
Protocol Buffers** 250 -10.07%
RMI (using Object Serialization for estimate)** 905 225.54%
REST — JSON 559 101.08%
REST — XML 836 200.72%

*Smaller is better.
** Excludes messaging overhead. Includes only transported objects.

Thrift has a clear advantage in the size of its payload particularly compared to RMI and XML-based REST. Protocol Buffers from Google is effectively the same given that the Protocol Buffers number excludes messaging overhead.

Runtime Performance

To compare the runtime performance of Thrift, I created the following scenario:

Test Scenario

  • Query the list of Course numbers.
  • Fetch the course for each course number.

This scenario is executed 10,000 times. The tests were run on the following systems:

Server Client
Operating System Ubuntu® Linux® 8.04 (hardy)
CPU Intel® Core™ 2 T5500 @ 1.66 GHz
Memory 2GiB
Cores 2
Window System Shutdown – To avoid any unnecessary spikes from other processes during execution.
Java Version Sun® Java™ SE Runtime Environment (build 1.6.0_14-b08)
Operating System Ubuntu® Linux® 8.04 (hardy)
CPU Intel® Pentium™ 4 @ 2.40 GHz
Memory 1GiB
Cores 1
Window System Shutdown – To avoid any unnecessary spikes from other processes during execution.
Java Version Sun® Java™ SE Runtime Environment (build 1.6.0_14-b08)

The following table describes each test run:

Method Description
Thrift Complete Thrift stack
Protocol Buffers* Custom server using normal, blocking socket I/O.
RMI Standard RMI
REST — XML & JSON Jersey running inside a Jetty server.

*Since Protocol Buffers does not include a services infrastructure (unlike Thrift), I wrote my own server for this article.

The chart and table below summarize the results. All times are in seconds.

Server CPU % Comparison Chart
Execution Time Comparison Chart
Server CPU % Avg Client CPU % Avg Wall Time
REST — XML 12.00% 80.75% 05:27.45
REST — JSON 20.00% 75.00% 04:44.83
RMI 16.00% 46.50% 02:14.54
Protocol Buffers 30.00% 37.75% 01:19.48
Thrift — TBinaryProtocol 33.00% 21.00% 01:13.65
Thrift — TCompactProtocol 30.00% 22.50% 01:05.12

*Average Time excludes the first run in order to account for server warm-up. Smaller numbers are better.

The tests yielded some interesting observations. In terms of wall time Thrift clearly out-performed REST and RMI. In fact, TCompactProtocol took less than 20% of the time it took REST-XML to transmit the same data. The clear dominance of the binary protocols should not be too surprising, as binary data transmission is well-known to have higher performance than text-based protocols. RMI in fact significantly out-performed JSON-based REST in wall time, despite its significantly larger payload size (61.9% larger).

The CPU percentages yielded some interesting numbers. While the Thrift and Protocol Buffers servers had the highest server CPU percentages, the REST clients had the highest CPU percentages of the clients. For whatever reason Thrift and REST disproportionately place their CPU loads on their clients and servers. Protocol Buffers balanced its load most evenly between client and server, but then again this was a simple quick hand-rolled server that I wrote for this article. While I did not have time to analyze the cause of the CPU load, the Thrift and Protocol Buffers examples needed to do manual conversion of objects between what is transmitted and what is used. The RMI and REST implementations required no such object conversion. This extra bit of work may account for the additional CPU utilization on the Thrift and Protocol Buffers servers.

This test basically just covered throughput of each server. Another useful test would have been too test how each server handled multiple concurrent connections of shorter duration. There was insufficient time to complete a concurrent test for this article, however.

Given the poor performance of REST, there may certainly be higher performing servlet containers than Jetty that could be used as part of this test. Jetty was merely chosen because of its relative ease in implementation and ease in bundling for download of the sample code used in this article. Doing some quick searches, I found one performance comparison that showed Apache Tomcat to be faster than Jetty, and another that showed them at parity. Neither study showed anywhere near a performance difference to make up for the wall time performance of the binary protocols.

All of these technologies are roughly equivalent in the amount of coding complexity required to make them work. This excludes Protocol Buffers of course, as it contains no services infrastructure. It should also be noted that Thrift generates all the code you need for a client or server for each language it supports. Java was the server of choice in this article, but other languages could be used if they are better suited – one of the main reasons Thrift was developed in the first place. That being said, I found many of the implementations incomplete. As mentioned previously, the Python implmentation for instance only had the TBinaryProtocol implemented.

Conclusion

Thrift is a powerful library for creating high-performance services that can be called from multiple languages. If your application has a need for multiple languages to communicate where speed is a concern and your clients and servers are co-located, Thrift may well be the choice for you. Thrift might also make a good choice for IPC on a single machine where speed and/or interoperability are a concern.

Thrift was designed for use where clients and servers are co-located, as in a data center. If you consider using Thrift in environments where client and server are not co-located, you should expect to encounter some challenges. In particular, the aforementioned issue with asynchronous calls, as well as lack of security are likely to pose challenges. While the security issue may be solved by a new transport, the issue with asynchronous calls will likely require work in the core areas of Thrift. Plus, since Thrift supports numerous language bindings, you will likely need to make changes for each language you are using.

If the composite service work-around will not work for you, the server-per-service limitation in Thrift may pose a problem in some deployment scenarios. For instance, if the Thrift services are on one side of a firewall and the client’s are on the other, some data centers may have a problem with opening up too many ports.

References

 
Bình luận về bài viết này

Posted by trên Tháng Mười 20, 2011 in Technology

 

Xây dựng Linux router và proxy với iptables + squid

Linux router là một hệ thống đứng giữa Internal network và Internet hoặc phân cách một network và một network khác. Linux router là một firewall được xây dựng bằng Netfilter/iptables, luôn có sẵn trên hầu hết các bản phân phối của Linux.

Nếu bạn là một người dùng bình thường truy cập internet thông qua modem thông thường, bạn không cần thiết phải có một firewall vì IP của bạn thường xuyên thay đổi, bạn không sử dụng một dịch vụ nào đặc biệt cần được bảo vệ, và hạn chế bởi một tường lửa chuyên dụng.

Đối với môi trường doanh nghiệp, việc triển khai nhiều hệ thống, dịch vụ cần sự bảo mật, và tối ưu trong quản lí, cả mạng Lan và các truy xuất từ bên ngoài vào, tường lửa là một nhu cầu thiết yếu và tối cần thiết. Các sản phẩm router phần cứng thường rất mắc và cần sự hỗ trợ tương đối từ phía cung cấp, trong khi việc xây dựng riêng một Linux firewall là việc không quá khó, tốn ít tài nguyên + tiền bạc, mà còn có thể xây dựng một hệ thống all in one tùy theo nhu cầu và mục đích, quản lí chặt chẽ và quan trọng là khả năng bền bỉ mà bản thân Linux mang lại.

Bài viết này sẽ cố gắng đưa ra một mô hình cụ thể, cách cài đặt và vận hành một Linux Firewall, transparent proxy, DHCP server trên một máy chủ Linux.

Mô hình thực hiện với máy chủ có cấu hình : Pentium Dual Core 2.8 GB, 1GB Ram và 80GB hard disk, sử dụng centos 5.3 enterprise với các gói cài đặt chuẩn, không có giao diện đồ họa.

Trước tiên hãy mô tả một chút về mô hình network mà ta cố gắng xây dựng: Một công ty sử dụng đường truyền cáp quang từ FPT, với đường truyền này công ty được cung cấp một đia chỉ IP tĩnh 111.111.111.111, đường truyền đi qua một router, sau đó đi vào mạng bên trong. Nhu cầu của công ty cần một vài dịch vụ web, mail, ftp, vpn….và chia cách, quản lí truy cập từ bên ngoài vào LAN bằng một firewall, proxy.

  1. Kiểm sóat luồng truy cập từ bên ngoài vào mạng LAN.
  2. Cấm các truy cập bất hợp lệ từ LAN ra internet
  3. Cấm sử dụng các dịch vụ chat (Instant message) từ LAN.
  4. Xây dựng một DHCP server tự động cung cấp IP cho mạng LAN.

Mô hình mạng được miêu tả như hình:
alt

Cách thức họat động: Linux router có 2 netwok cards:

eth0 là địa chỉ mặt ngoài tiếp xúc với Modem có địa chỉ là 192.168.1.2

eth1 là địa chỉ mặt trong tiếp xúc với LAN có địa chỉ là 172.16.0.1

Trên Router ta triển khai DHCP server cung cấp IP cho toàn bộ mạng LAN. Cũng trên server này ta xây dựng router và squid proxy để thực hiện các yêu cầu đưa ra.

  1. DHCP server: Đây là dịch vụ đơn giản và dễ dàng nhất trong bài viết này. Gói cài đặt được cung cấp sẵn + một vài thay đổi nhỏ trong cấu hình là ta đã cấp được IP cho toàn bộ mạng LAN.

Dùng yum để download và cài đặt dhcpd:

  1. [hungnv@home ~]$ sudo yum
  2. install dpcp dhcp-devel
  3. [sudo] password for hungnv:

Mở file /etc/dhcpd.conf, xóa trắng nếu có nội dung và thêm vào tương tự:

  1. [hungnv@home ~]$ vim /etc/dhcpd.conf
  2. #
  3. # DHCP Server Configuration file.
  4. # see /usr/share/doc/dhcp*/dhcpd.conf.sample
  5. #
  6. ddns-update-style none;
  7. deny bootp;
  8. authoritstive;
  9. subnet 172.16.0.0 netmask 255.255.0.0
  10. {
  11. option subnet-mask 255.255.0.0;
  12. option domain-name “opensource.com.vn”;
  13. option routers 172.16.0.1;
  14. option domain-name-servers ns1.opensource.com.vn, ns2.opensource.com.vn, dns.fpt.vn;
  15. #option netbios-name-servers 192.168.0.2;
  16. range dynamic-bootp 172.16.0.20 172.16.0.120;
  17. default-lease-time 31200;
  18. max-lease-time 62400;
  19. }

Khởi động dhcp server

  1. [root @home ~]#
  2. service dhcpd restart
  3. Shutting down dhcpd: [ OK ]
  4. Starting dhcpd: [ OK ]

Ở client, dùng dhclient để lấy IP:

  1.  [user@client~]# dhclient eth0

Sau đó dùng ifconfig để xem IP hiện tại của client này.

Squid như là một transparent proxy:

Cài đặt:

  1. #yum install squid

Mở file /etc/squid/squid.conf

Thêm vào nội dung bên dưới

  1. acl manager proto cache_object
  2. acl localhost src 127.0.0.1/32
  3. acl localnet src 172.16.0.0/16
  4. acl all src 0/0
  5. http_port 172.16.0.1:1234
  6. # Define safe ports to be allowed by transparent proxy
  7. acl SSL_ports port 443 563
  8. acl Safe_ports port 25 #smtp server
  9. acl Safe_ports port 143 #Imap mail server
  10. acl Safe_ports port 6789 #ssh mapped port
  11. acl Safe_ports port 80 # http
  12. acl Safe_ports port 21 # ftp
  13. acl Safe_ports port 443 # https
  14. acl Safe_ports port 1025-65535 # unregistered ports
  15. acl Safe_ports port 6222 #Jabber server
  16. acl Safe_ports port 993 #imap over ssl
  17. acl Safe_ports port 7025 #Local mail delivery
  18. acl Safe_ports port 7036 #Mysql local
  19. acl Safe_ports port 7071 #Zimbra admin cosole
  20. # Deny yahoo messsenger
  21. # Yahoo! Messenger
  22. acl ym dstdomain .yahoo.com
  23. acl ym dstdomain .us.il.yimg.com .msg.yahoo.com .pager.yahoo.com
  24. acl ym dstdomain .rareedge.com .ytunnelpro.com .chat.yahoo.com
  25. acl ym dstdomain .voice.yahoo.com .address.yahoo.com
  26. acl ymregex url_regex yupdater.yim ymsgr myspaceim
  27. #Other protocols Yahoo!Messenger uses ??
  28. acl ym dstdomain .skype.com .imvu.com
  29. http_access deny ym
  30. http_access deny ymregex
  31. acl CONNECT method CONNECT
  32. http_access allow localnet
  33. http_access allow manager localhost
  34. http_access allow proxypass
  35. http_access deny manager
  36. http_access deny !Safe_ports
  37. http_access deny CONNECT !SSL_ports
  38. http_access deny all
  39. icp_access deny all
  40. hierarchy_stoplist cgi-bin ?
  41. cache_mem 256 MB
  42. cache_dir ufs /var/spool/squid 2048 16 256
  43. cache_mgr hungnv@opensource.com.vn
  44. cache_effective_user squid
  45. cache_effective_group squid
  46. access_log /var/log/squid/access.log squid
  47. refresh_pattern ^ftp: 1440 20% 10080
  48. refresh_pattern ^gopher: 1440 0% 1440
  49. refresh_pattern (cgi-bin|\?) 0 0% 0
  50. refresh_pattern . 0 20% 4320
  51. visible_hostname opensource.com.vn
  52. icp_port 3130
  53. always_direct allow all
  54. forwarded_for off
  55. coredump_dir /var/spool/squid

Trong đoạn cấu hình trên, ta đã cấu hình cho squid chặn các dịch vụ IM như yahoo và skype, danh sách các protocol bị chặn các bạn có thể xem ở: http://wiki.squid-cache.org/ConfigExamples/ phần instant message.

Tiếp theo là phần cài đặt router. Nếu ta đã biết rõ được nhu cầu của mình cần gì và có được sơ đồ những dịch vụ cần thiết, việc thiết lập router/firewall là không hề khó. Iptables là dịch vụ có sẵn trên centos, cho nên ta không cần phải cài đặt.

  1.  #!/bin/sh
  2. # Script makes a linux box acts as an Linux Router.
  3. # 22-10-2009
  4. # By hungnv@opensource.com.vn and some others from the internet 🙂
  5. #
  6. echo -e “\n\n\n Installing iptables script…”
  7. # Name of Internal Interface
  8. LANIF=”eth1″
  9. # Enter Lan Network
  10. IPLAN=”172.16.0.0/16″
  11. # ipaddress of internal interface
  12. LANIP=”172.16.0.1/16″
  13. # Name of External Interface
  14. WANIF=”eth0″
  15. # ipaddress of external interface
  16. EXTIP=”192.168.1.2″
  17. echo “Preparing…”
  18. /sbin/depmod -a
  19. /sbin/modprobe ip_tables
  20. /sbin/modprobe ip_conntrack
  21. /sbin/modprobe ip_conntrack_ftp
  22. /sbin/modprobe ip_conntrack_irc
  23. /sbin/modprobe iptable_nat
  24. /sbin/modprobe ip_nat_ftp
  25. /sbin/modprobe ip_nat_irc
  26. echo ” Enabling IP forwarding…”
  27. echo “1” > /proc/sys/net/ipv4/ip_forward
  28. echo “1” > /proc/sys/net/ipv4/ip_dynaddr
  29. echo ” External interface: $EXTIF”
  30. echo ” External interface IP address is: $EXTIP”
  31. echo ” Loading firewall server rules…”
  32. ALL=”0.0.0.0/0″
  33. # Set default policy to DROP
  34. iptables -P INPUT DROP
  35. iptables -F INPUT
  36. iptables -P OUTPUT DROP
  37. iptables -F OUTPUT
  38. iptables -P FORWARD DROP
  39. iptables -F FORWARD
  40. iptables -F -t nat
  41. # Flush the user chain.. if it exists
  42. if [ “`iptables -L | grep drop-and-log-it`” ]; then
  43. iptables -F drop-and-log-it
  44. fi
  45. # Delete all User-specified chains
  46. iptables -X
  47. # Reset all IPTABLES counters
  48. iptables -Z
  49. # Creating a DROP chain
  50. iptables -N drop-and-log-it
  51. iptables -A drop-and-log-it -j LOG –log-level info
  52. iptables -A drop-and-log-it -j REJECT
  53. echo -e ” – Loading INPUT rulesets”
  54. # loopback interfaces are valid.
  55. iptables -A INPUT -i lo -s $ALL -d $ALL -j ACCEPT
  56. # local interface, local machines, going anywhere is valid
  57. iptables -A INPUT -i $INTIF -s $INTNET -d $ALL -j ACCEPT
  58. # remote interface, claiming to be local machines, IP spoofing, get lost
  59. iptables -A INPUT -i $EXTIF -s $INTNET -d $ALL -j drop-and-log-it
  60. # remote interface, any source, going to permanent PPP address is valid
  61. iptables -A INPUT -i $EXTIF -s $ALL -d $EXTIP -j ACCEPT
  62. # Allow any related traffic coming back to the MASQ server in
  63. iptables -A INPUT -i $EXTIF -s $ALL -d $EXTIP -m state –state ESTABLISHED,RELATED -j ACCEPT
  64. # Catch all rule, all other incoming is denied and logged.
  65. iptables -A INPUT -s $ALL -d $ALL -j drop-and-log-it
  66. echo -e ” – Loading OUTPUT rulesets”
  67. #######################################################################
  68. # OUTPUT: Outgoing traffic from various interfaces. All rulesets are
  69. # already flushed and set to a default policy of DROP.
  70. #
  71. # loopback interface is valid.
  72. iptables -A OUTPUT -o lo -s $ALL -d $ALL -j ACCEPT
  73. # local interfaces, any source going to local net is valid
  74. iptables -A OUTPUT -o $INTIF -s $EXTIP -d $INTNET -j ACCEPT
  75. # local interface, any source going to local net is valid
  76. iptables -A OUTPUT -o $INTIF -s $INTIP -d $INTNET -j ACCEPT
  77. # outgoing to local net on remote interface, stuffed routing, deny
  78. iptables -A OUTPUT -o $EXTIF -s $ALL -d $INTNET -j drop-and-log-it
  79. # anything else outgoing on remote interface is valid
  80. iptables -A OUTPUT -o $EXTIF -s $EXTIP -d $ALL -j ACCEPT
  81. # Catch all rule, all other outgoing is denied and logged.
  82. iptables -A OUTPUT -s $ALL -d $ALL -j drop-and-log-it
  83. echo -e ” – Loading FORWARD rulesets”
  84. iptables -A FORWARD -i $EXTIF -o $INTIF -m state –state ESTABLISHED,RELATED -j ACCEPT
  85. iptables -A FORWARD -i $INTIF -o $EXTIF -j ACCEPT
  86. # Catch all rule, all other forwarding is denied and logged.
  87. iptables -A FORWARD -j drop-and-log-it
  88. # Enable SNAT (MASQUERADE) functionality on $EXTIF
  89. iptables -t nat -A POSTROUTING -o $EXTIF -j SNAT –to $EXTIP
  90. #######################################################################
  91. # For Squid Server
  92. ###
  93. #
  94. INTIP=”172.16.0.1″
  95. iptables -t nat -A PREROUTING -i $INTIF –protocol tcp –dport 80 -j DNAT –to $INTIP:$SQUID_PORT
  96. # if it is same system
  97. iptables -t nat -A PREROUTING -i $EXTIF –protocol tcp –dport 80 -j REDIRECT –to-port $SQUID_PORT
  98. echo -e ” Setting up firewall complete\n\n”

Lưu script trên với tên tùy ý, cho tùy chọn execute ( +x ) và đặt vào /etc/rc.local để script chạy lúc khởi động,

Lúc này bạn test thử truy cập internet và thêm vào các phần khác theo nhu cầu riêng của mình.

Xin lưu ý rằng các script cũng như lệnh nói đến trong bài viết này chỉ nhằm mục đích tạo ra một dhcp server, proxy và firewall “có thể” làm việc, không hề có bất cứ tính năng gì kèm theo. Muốn đầy đủ, phải hiểu được nhu cầu cụ thể của từng môi trường, như tiêu chí của bài viết nói đến.

nguồn:http://opensource.com.vn/opensource/linux/centos/106-xay-dung-linux-router-va-proxy-voi-iptables-squid.html

 
Bình luận về bài viết này

Posted by trên Tháng Chín 12, 2011 in Technology